From 975eb225097cc2504709f5c13bf44f3a8477845c Mon Sep 17 00:00:00 2001 From: Supun Wimalasena Date: Mon, 23 Feb 2026 12:03:53 +0100 Subject: [PATCH 1/3] subscribers search box fix --- .../routes/console/(nav)/[subdomain]/subscribers/+page.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/routes/console/(nav)/[subdomain]/subscribers/+page.svelte b/frontend/src/routes/console/(nav)/[subdomain]/subscribers/+page.svelte index 953e92be..416dcc64 100644 --- a/frontend/src/routes/console/(nav)/[subdomain]/subscribers/+page.svelte +++ b/frontend/src/routes/console/(nav)/[subdomain]/subscribers/+page.svelte @@ -198,6 +198,7 @@ on:keydown={searchActions.onKeydown} on:blur={searchActions.onBlur} size="small" + block={false} > {#snippet end()} {#if search !== searchVal} From fd5415307be663ae9d4d1735bc80b46b27278e7c Mon Sep 17 00:00:00 2001 From: Supun Wimalasena Date: Mon, 23 Feb 2026 12:19:22 +0100 Subject: [PATCH 2/3] fixes #430 --- .../Api/Console/Controller/UserController.php | 8 +- backend/src/Service/User/UserService.php | 7 +- frontend/package-lock.json | 39 +- .../[subdomain]/settings/users/+page.svelte | 5 +- .../settings/users/CreateAccountLink.svelte | 8 - .../settings/users/UserInvitationModal.svelte | 37 +- .../routes/console/lib/actions/userActions.ts | 2 +- shared/locale/en.json | 769 +++++++++--------- 8 files changed, 429 insertions(+), 446 deletions(-) delete mode 100644 frontend/src/routes/console/(nav)/[subdomain]/settings/users/CreateAccountLink.svelte diff --git a/backend/src/Api/Console/Controller/UserController.php b/backend/src/Api/Console/Controller/UserController.php index 65dc3d5a..95da9aab 100644 --- a/backend/src/Api/Console/Controller/UserController.php +++ b/backend/src/Api/Console/Controller/UserController.php @@ -66,6 +66,10 @@ public function createUser(Newsletter $newsletter, #[MapRequestPayload] CreateUs $organizationId = $newsletter->getOrganizationId(); assert($organizationId !== null); + if ($this->userService->hasAccessToNewsletter($newsletter, $hyvorUser->id)) { + throw new BadRequestHttpException('User is already added to the newsletter'); + } + try { $verification = $this->comms->send( new VerifyMember( @@ -81,10 +85,6 @@ public function createUser(Newsletter $newsletter, #[MapRequestPayload] CreateUs throw new BadRequestHttpException('Unable to find the user in the organization'); } - if ($this->userService->isAdmin($newsletter, $hyvorUser->id)) { - throw new BadRequestHttpException("User is already an admin"); - } - $newsletterUser = $this->userService->createUser($newsletter, $hyvorUser->id); return $this->json( diff --git a/backend/src/Service/User/UserService.php b/backend/src/Service/User/UserService.php index ca568f65..683d9fd1 100644 --- a/backend/src/Service/User/UserService.php +++ b/backend/src/Service/User/UserService.php @@ -52,13 +52,10 @@ public function isAdmin(Newsletter $newsletter, int $hyvorUserId): bool $user = $this->em->getRepository(User::class)->findBy([ 'newsletter' => $newsletter, 'hyvor_user_id' => $hyvorUserId, - 'role' => UserRole::ADMIN->value + 'role' => UserRole::ADMIN ]); - if (!$user) { - return false; - } - return true; + return !!$user; } public function createUser(Newsletter $newsletter, int $hyvorUserId): User diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ba75952b..9a85bc64 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -725,7 +725,6 @@ "resolved": "https://registry.npmjs.org/@hyvor/design/-/design-2.0.5.tgz", "integrity": "sha512-sblejBFpulrO1+j3mj3d8JU7svsqQrBs7OYDm9nSGIOgczUcLvVM84p5g4mSJ8qdawCTuFZTplzLchEld0FuMg==", "license": "MIT", - "peer": true, "dependencies": { "@fontsource/readex-pro": "^5.2.11", "@hyvor/icons": "^1.1.1", @@ -748,7 +747,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@hyvor/icons/-/icons-1.1.1.tgz", "integrity": "sha512-8g08HWBNsWuCOzQHUqB2jRSv/XzA82NlKWm/x/NVz7G5lQD0wJibJLyzNmlYVDhwQh6TyjKcA2yR1f4eRqC1Ew==", - "peer": true, "peerDependencies": { "svelte": "^5.0.0" } @@ -851,6 +849,7 @@ "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.3", "is-glob": "^4.0.3", @@ -892,6 +891,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -912,6 +912,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -932,6 +933,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -952,6 +954,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -972,6 +975,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -992,6 +996,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1012,6 +1017,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1032,6 +1038,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1052,6 +1059,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1072,6 +1080,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1092,6 +1101,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1112,6 +1122,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1132,6 +1143,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 10.0.0" }, @@ -1584,7 +1596,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.1.tgz", "integrity": "sha512-XRHD2i3zC4ukhz2iCQzO4mbsts081PAZnnMAQ7LNpWeYgeBmwMsalf0FGSwhFXBbtr2XViPKnFJBDCckWqrsLw==", "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -1649,7 +1660,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz", "integrity": "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==", "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", @@ -1790,7 +1800,6 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2013,7 +2022,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2169,7 +2177,6 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", - "peer": true, "dependencies": { "@kurkle/color": "^0.3.0" }, @@ -2359,6 +2366,7 @@ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -2469,7 +2477,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -2832,7 +2839,8 @@ "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/import-fresh": { "version": "3.3.1", @@ -3195,7 +3203,8 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/obug": { "version": "2.1.1", @@ -3407,7 +3416,6 @@ "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -3502,7 +3510,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" @@ -3523,7 +3530,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", "license": "MIT", - "peer": true, "dependencies": { "orderedmap": "^2.0.0" } @@ -3544,7 +3550,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", @@ -3578,7 +3583,6 @@ "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.5.tgz", "integrity": "sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==", "license": "MIT", - "peer": true, "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -3730,6 +3734,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", + "peer": true, "dependencies": { "readdirp": "^4.0.1" }, @@ -3745,6 +3750,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 14.18.0" }, @@ -3894,7 +3900,6 @@ "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.49.1.tgz", "integrity": "sha512-jj95WnbKbXsXXngYj28a4zx8jeZx50CN/J4r0CEeax2pbfdsETv/J1K8V9Hbu3DCXnpHz5qAikICuxEooi7eNQ==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -4105,7 +4110,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4249,7 +4253,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", diff --git a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/+page.svelte b/frontend/src/routes/console/(nav)/[subdomain]/settings/users/+page.svelte index fb1be193..7dd8f188 100644 --- a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/+page.svelte +++ b/frontend/src/routes/console/(nav)/[subdomain]/settings/users/+page.svelte @@ -43,10 +43,7 @@ {#if inviterOpen} - (users = [...users, u])} - /> + (users = [...users, u])} /> {/if} diff --git a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/CreateAccountLink.svelte b/frontend/src/routes/console/(nav)/[subdomain]/settings/users/CreateAccountLink.svelte deleted file mode 100644 index eae90055..00000000 --- a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/CreateAccountLink.svelte +++ /dev/null @@ -1,8 +0,0 @@ - - - - {children} - diff --git a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/UserInvitationModal.svelte b/frontend/src/routes/console/(nav)/[subdomain]/settings/users/UserInvitationModal.svelte index aae46f98..f5a59b97 100644 --- a/frontend/src/routes/console/(nav)/[subdomain]/settings/users/UserInvitationModal.svelte +++ b/frontend/src/routes/console/(nav)/[subdomain]/settings/users/UserInvitationModal.svelte @@ -6,59 +6,62 @@ import { OrganizationMemberSearch } from '@hyvor/design/cloud'; import type { User } from '../../../../types'; - export let show: boolean; - export let refreshUsers: (u: User) => void; + interface Props { + show: boolean; + onadd: (u: User) => void; + } + + let { show = $bindable(), onadd }: Props = $props(); - let isInviting = false; - let invitingUserId: number | undefined = undefined; + let isInviting = $state(false); + let addingUserId: number | undefined = $state(undefined); const I = getI18n(); async function handleInvite() { - if (!invitingUserId) { + if (!addingUserId) { + toast.error('Please choose a member'); return; } isInviting = true; addUser({ - user_id: invitingUserId, + user_id: addingUserId, role: 'admin' // Hardcoded for now }) .then((user) => { - refreshUsers(user); - toast.success(I.t('console.settings.users.inviteSent')); + onadd(user); + toast.success(I.t('console.settings.users.added')); + show = false; }) .catch((e) => { toast.error(e.message); }) .finally(() => { isInviting = false; - show = false; }); } - + - - diff --git a/frontend/src/routes/console/lib/actions/userActions.ts b/frontend/src/routes/console/lib/actions/userActions.ts index 238ef4d2..3abc939d 100644 --- a/frontend/src/routes/console/lib/actions/userActions.ts +++ b/frontend/src/routes/console/lib/actions/userActions.ts @@ -14,7 +14,7 @@ interface AddUserInput { export function addUser(data: AddUserInput) { return consoleApi.post({ - endpoint: 'invites', + endpoint: 'users', data }); } diff --git a/shared/locale/en.json b/shared/locale/en.json index 8ec9c940..80b50ec8 100644 --- a/shared/locale/en.json +++ b/shared/locale/en.json @@ -1,400 +1,391 @@ { - "console": { - "common": { - "cancel": "Cancel", - "close": "Close", - "search": "Search", - "copy": "Copy", - "copied": "{value} copied", - "cannotBeEmpty": "{field} cannot be empty", - "created": "{field} created", - "updated": "{field} updated", - "deleted": "{field} deleted", - "loadMore": "Load More", - "createField": "Create {field}", - "updateField": "Update {field}", - "deleteField": "Delete {field}", - "editField": "Edit {field}", - "failedToLoadField": "Failed to load {field}", - "create": "Create", - "update": "Update", - "delete": "Delete", - "yes": "Yes", - "any": "Any", - "save": "Save", - "actionConfirm": { - "title": "Confirmation", - "content": "Are you sure you want to perform this action?" - }, - "selectAll": "Select All", - "deselectAll": "Deselect All" - }, - "nav": { - "approve": "Approval", - "domains": "Domains", - "billing": "Billing", - "billingTooltip": "Your account must be approved to access billing", - "home": "Home", - "subscribers": "Subscribers", - "issues": "Issues", - "tools": "Tools", - "install": "Install", - "settings": "Settings" - }, - "approve": { - "preferred": "Preferred", - "companyName": "Company Name", - "companyNameCaption": "Or, if you are an individual, your full name.", - "country": "Country", - "website": "Website", - "socialLinks": "Social Media Links", - "typeOfContent": "Type of Content", - "typeOfContentCaption": "What type of content do you plan to have on your newsletter?", - "typeOfContentPlaceholder": "Weekly insights about tech, AI, and startups", - "frequency": "Issues per Month", - "frequencyCaption": "How many newsletter issues do you plan to send per month?", - "frequencyPlaceholder": "10 issues/month", - "existingList": "Existing Email List", - "existingListCaption": "Do you already have an email list that you wish to import? If yes, how did you obtain it? How many subscribers are there?", - "sample": "Sample Email", - "sampleCaption": "Link to a sample email or past newsletter archive, if available.", - "whyPost": "Why Hyvor Post?", - "whyPostCaption": "Completely optional. Our team would love to know why you chose Hyvor Post.", - "checkbox1": "I agree that Hyvor Post does not allow sending unsolicited emails or cold emails, and every subscriber must have opted in to receive emails.", - "checkbox2": "I agree to the Terms of Service and Privacy Policy of Hyvor Post.", - "submit": "Submit for Approval", - "update": "Update Info", - "companyNameRequired": "Company name is required", - "countryRequired": "Country is required", - "websiteRequired": "Website is required", - "typeOfContentRequired": "Type of Content is required", - "websiteInvalid": "Website URL is invalid", - "checkboxRequired": "You must agree to the terms and conditions", - "submittedForApproval": "Your information has been submitted for approval.", - "updatedInfo": "Your information has been updated.", - "error": "An error occurred while submitting your information. Please try again.", - "pendingNotice": "Your account must be approved by our team before sending newsletters. Please fill out the following form, and we will review it within 24 hours.", - "markedAsPendingNotice": "Your approval request has been marked as pending by our team. You may update your information and resubmit for approval.", - "reviewNotice": "Your approval request is currently under review. You may make any changes and resubmit if you need to update your information. The review will be completed within 24 hours.", - "rejectNotice": "Your approval request has been rejected. Please contact support for any further clarifications.", - "rejectReason": "Reject reason: {reason}", - "approvedNotice": "Your account was approved on {date}" - }, - "domains": { - "addDomain": "Add Domain", - "firstDomain": "Add a domain to send newsletters from your own domain." - }, - "billing": { - "usage": { - "title": "Usage", - "licenseDoesNotIncludeEmails": "Your current license does not include sending emails", - "30days": "30d" - }, - "approvalRequired": "Your account must be approved to create a subscription." - }, - "home": { - "stats": { - "bouncedRate": "Bounced Rate", - "complaintRate": "Complaint Rate" - } - }, - "lists": { - "lists": "Lists", - "list": "List", - "tooltip": "Lists are used to group subscribers. You can send issues to one or more lists." - }, - "subscribers": { - "addSubscribers": "Add Subscribers", - "emptyList": "No subscribers found", - "status": { - "label": "Status", - "all": "All", - "subscribed": "Subscribed", - "unsubscribed": "Unsubscribed", - "pending": "Pending" - }, - "count": "{count} subscribers", - "lists_count": "{count} lists", - "source": { - "label": "Source", - "console": "Console", - "form": "Signup Form", - "import": "Imported" - }, - "bulk": { - "deselect": "Deselect", - "deleteConfirm": "Are you sure you want to delete these subscribers?", - "deleteTitle": "Delete Subscriber", - "deleteSuccess": "Subscribers deleted successfully", - "statusUpdateTitle": "Update Status", - "statusUpdateConfirm": "Are you sure you want to change the status to {status}?", - "statusUpdateSuccess": "Subscribers status updated successfully", - "metadataUpdateSuccess": "Subscribers metadata updated successfully", - "updateStatus": "Update Status", - "selectAllWarning": "You can only select up to {count} subscribers at once" - } - }, - "issues": { - "newIssue": "New Issue", - "noIssuesSent": "No issues sent yet", - "createFirstIssue": "Create your first issue", - "draft": { - "subject": "Subject", - "subjectPlaceholder": "Subject...", - "lists": "Lists", - "listsEmptyError": "Please select at least one list", - "subscribersCount": "{count} {count, plural, one {subscriber} other {subscribers}}", - "sendableSubscribersCount": "{count} {count, plural, one {subscriber} other {subscribers}} will receive this issue", - "fromName": "From Name", - "fromEmail": "From Email", - "fromEmailCaption": "Your subscribers will see this email address as the sender of the newsletter.", - "replyTo": "Reply To Email", - "steps": { - "content": "Content", - "audience": "Audience & Send" + "console": { + "common": { + "cancel": "Cancel", + "close": "Close", + "search": "Search", + "copy": "Copy", + "copied": "{value} copied", + "cannotBeEmpty": "{field} cannot be empty", + "created": "{field} created", + "updated": "{field} updated", + "deleted": "{field} deleted", + "loadMore": "Load More", + "createField": "Create {field}", + "updateField": "Update {field}", + "deleteField": "Delete {field}", + "editField": "Edit {field}", + "failedToLoadField": "Failed to load {field}", + "create": "Create", + "update": "Update", + "delete": "Delete", + "yes": "Yes", + "any": "Any", + "save": "Save", + "actionConfirm": { + "title": "Confirmation", + "content": "Are you sure you want to perform this action?" + }, + "selectAll": "Select All", + "deselectAll": "Deselect All" + }, + "nav": { + "approve": "Approval", + "domains": "Domains", + "billing": "Billing", + "billingTooltip": "Your account must be approved to access billing", + "home": "Home", + "subscribers": "Subscribers", + "issues": "Issues", + "tools": "Tools", + "install": "Install", + "settings": "Settings" + }, + "approve": { + "preferred": "Preferred", + "companyName": "Company Name", + "companyNameCaption": "Or, if you are an individual, your full name.", + "country": "Country", + "website": "Website", + "socialLinks": "Social Media Links", + "typeOfContent": "Type of Content", + "typeOfContentCaption": "What type of content do you plan to have on your newsletter?", + "typeOfContentPlaceholder": "Weekly insights about tech, AI, and startups", + "frequency": "Issues per Month", + "frequencyCaption": "How many newsletter issues do you plan to send per month?", + "frequencyPlaceholder": "10 issues/month", + "existingList": "Existing Email List", + "existingListCaption": "Do you already have an email list that you wish to import? If yes, how did you obtain it? How many subscribers are there?", + "sample": "Sample Email", + "sampleCaption": "Link to a sample email or past newsletter archive, if available.", + "whyPost": "Why Hyvor Post?", + "whyPostCaption": "Completely optional. Our team would love to know why you chose Hyvor Post.", + "checkbox1": "I agree that Hyvor Post does not allow sending unsolicited emails or cold emails, and every subscriber must have opted in to receive emails.", + "checkbox2": "I agree to the Terms of Service and Privacy Policy of Hyvor Post.", + "submit": "Submit for Approval", + "update": "Update Info", + "companyNameRequired": "Company name is required", + "countryRequired": "Country is required", + "websiteRequired": "Website is required", + "typeOfContentRequired": "Type of Content is required", + "websiteInvalid": "Website URL is invalid", + "checkboxRequired": "You must agree to the terms and conditions", + "submittedForApproval": "Your information has been submitted for approval.", + "updatedInfo": "Your information has been updated.", + "error": "An error occurred while submitting your information. Please try again.", + "pendingNotice": "Your account must be approved by our team before sending newsletters. Please fill out the following form, and we will review it within 24 hours.", + "markedAsPendingNotice": "Your approval request has been marked as pending by our team. You may update your information and resubmit for approval.", + "reviewNotice": "Your approval request is currently under review. You may make any changes and resubmit if you need to update your information. The review will be completed within 24 hours.", + "rejectNotice": "Your approval request has been rejected. Please contact support for any further clarifications.", + "rejectReason": "Reject reason: {reason}", + "approvedNotice": "Your account was approved on {date}" + }, + "domains": { + "addDomain": "Add Domain", + "firstDomain": "Add a domain to send newsletters from your own domain." + }, + "billing": { + "usage": { + "title": "Usage", + "licenseDoesNotIncludeEmails": "Your current license does not include sending emails", + "30days": "30d" + }, + "approvalRequired": "Your account must be approved to create a subscription." + }, + "home": { + "stats": { + "bouncedRate": "Bounced Rate", + "complaintRate": "Complaint Rate" + } }, - "sendingLimitReached": { - "title": "Sending Limit Reached", - "message": "You have reached your sending limit of {currentLimit} emails and this issue would exceed the limit by {exceedAmount} emails. Please upgrade your plan to send to more subscribers.", - "upgrade": "Upgrade" + "lists": { + "lists": "Lists", + "list": "List", + "tooltip": "Lists are used to group subscribers. You can send issues to one or more lists." }, - "sendingProfile": { - "title": "Sending Profile", - "system": "This is the system profile.", - "default": "This is the default sending profile.", - "noBranding": "No branding configured", - "brandLogo": "Brand Logo", - "configure": "Configure Sending Profiles" + "subscribers": { + "addSubscribers": "Add Subscribers", + "emptyList": "No subscribers found", + "status": { + "label": "Status", + "all": "All", + "subscribed": "Subscribed", + "unsubscribed": "Unsubscribed", + "pending": "Pending" + }, + "count": "{count} subscribers", + "lists_count": "{count} lists", + "source": { + "label": "Source", + "console": "Console", + "form": "Signup Form", + "import": "Imported" + }, + "bulk": { + "deselect": "Deselect", + "deleteConfirm": "Are you sure you want to delete these subscribers?", + "deleteTitle": "Delete Subscriber", + "deleteSuccess": "Subscribers deleted successfully", + "statusUpdateTitle": "Update Status", + "statusUpdateConfirm": "Are you sure you want to change the status to {status}?", + "statusUpdateSuccess": "Subscribers status updated successfully", + "metadataUpdateSuccess": "Subscribers metadata updated successfully", + "updateStatus": "Update Status", + "selectAllWarning": "You can only select up to {count} subscribers at once" + } }, - "testEmail": { - "title": "Send Test Email", - "description": "Ready to send your issue? Try sending a test email first to see how it looks in your inbox.", - "emailValidation": { - "emailNonEmpty": "Email cannot be empty", - "emailInvalid": "Invalid email address", - "emailNotAllowed": "The email must be from a newsletter user or a newsletter owner verified domain." - }, - "confirm": { - "sending": "Sending test email...", - "sentMultiple": "Test email successfully sent for {count} addresses", - "sent": "Test email successfully sent.", - "failed": "Failed to send test email." - }, - "modal": { - "to": "To", - "toPlaceholder": "Enter email address", - "suggested": "Suggested emails", - "suggestedCaption": "Emails of newsletter users", - "noSuggested": "No suggested emails" - } + "issues": { + "newIssue": "New Issue", + "noIssuesSent": "No issues sent yet", + "createFirstIssue": "Create your first issue", + "draft": { + "subject": "Subject", + "subjectPlaceholder": "Subject...", + "lists": "Lists", + "listsEmptyError": "Please select at least one list", + "subscribersCount": "{count} {count, plural, one {subscriber} other {subscribers}}", + "sendableSubscribersCount": "{count} {count, plural, one {subscriber} other {subscribers}} will receive this issue", + "fromName": "From Name", + "fromEmail": "From Email", + "fromEmailCaption": "Your subscribers will see this email address as the sender of the newsletter.", + "replyTo": "Reply To Email", + "steps": { + "content": "Content", + "audience": "Audience & Send" + }, + "sendingLimitReached": { + "title": "Sending Limit Reached", + "message": "You have reached your sending limit of {currentLimit} emails and this issue would exceed the limit by {exceedAmount} emails. Please upgrade your plan to send to more subscribers.", + "upgrade": "Upgrade" + }, + "sendingProfile": { + "title": "Sending Profile", + "system": "This is the system profile.", + "default": "This is the default sending profile.", + "noBranding": "No branding configured", + "brandLogo": "Brand Logo", + "configure": "Configure Sending Profiles" + }, + "testEmail": { + "title": "Send Test Email", + "description": "Ready to send your issue? Try sending a test email first to see how it looks in your inbox.", + "emailValidation": { + "emailNonEmpty": "Email cannot be empty", + "emailInvalid": "Invalid email address", + "emailNotAllowed": "The email must be from a newsletter user or a newsletter owner verified domain." + }, + "confirm": { + "sending": "Sending test email...", + "sentMultiple": "Test email successfully sent for {count} addresses", + "sent": "Test email successfully sent.", + "failed": "Failed to send test email." + }, + "modal": { + "to": "To", + "toPlaceholder": "Enter email address", + "suggested": "Suggested emails", + "suggestedCaption": "Emails of newsletter users", + "noSuggested": "No suggested emails" + } + }, + "approveBeforeSending": "Your account must be approved to send issues.", + "subscriptionBeforeSending": "You must have a valid subscription to send issues.", + "sendIssue": { + "title": "Send Issue Confirmation", + "content": "You are about to send this newsletter issue. Are you sure you want to send this? Please double-check everything before sending as you cannot undo this action.", + "confirmText": "Yes, send it", + "success": "Newsletter sent successfully", + "failed": "Failed to send newsletter", + "validate": { + "subject": "Subject is required", + "lists": "At least one list is required", + "content": "Content is required", + "license": "A valid subscription is required." + } + } + }, + "delete": { + "title": "Delete Issue", + "content": "Are you sure you want to delete this issue?", + "confirmText": "Yes, delete", + "success": "Issue deleted successfully" + } }, - "approveBeforeSending": "Your account must be approved to send issues.", - "subscriptionBeforeSending": "You must have a valid subscription to send issues.", - "sendIssue": { - "title": "Send Issue Confirmation", - "content": "You are about to send this newsletter issue. Are you sure you want to send this? Please double-check everything before sending as you cannot undo this action.", - "confirmText": "Yes, send it", - "success": "Newsletter sent successfully", - "failed": "Failed to send newsletter", - "validate": { - "subject": "Subject is required", - "lists": "At least one list is required", - "content": "Content is required", - "license": "A valid subscription is required." - } + "tools": { + "import": { + "title": "Import", + "status": { + "requires_input": "Requires Input", + "pending_approval": "Pending Approval", + "importing": "Importing", + "completed": "Completed", + "failed": "Failed" + }, + "showFields": "Show Fields", + "mappedFields": "Mapped Fields", + "seeWarnings": "See Warnings", + "importWarnings": "Import Warnings", + "notMapped": "Not Mapped", + "importedCount": "Imported {count} subscribers" + }, + "export": { + "title": "Export", + "status": { + "pending": "Pending", + "completed": "Completed", + "failed": "Failed" + } + } + }, + "settings": { + "form": { + "signupForm": "Signup Form", + "texts": "Texts", + "textsHtmlAllowed": "HTML is allowed", + "title": "Title", + "titleCaption": "Shown above the form", + "description": "Description", + "descriptionCaption": "Shown below the title", + "footerText": "Footer text", + "footerTextCaption": "Shown below the form", + "subscribeButtonText": "Subscribe Button Text", + "subscribeButtonTextCaption": "Text shown on the subscribe button", + "successMessage": "Success Message", + "successMessageCaption": "Message shown after successful subscription", + "customCss": "Custom CSS", + "colorsUi": "Colors & UI", + "paletteLight": "Light Palette", + "paletteDark": "Dark Palette", + "textColor": "Text Color", + "textColorCaption": "Used for most texts in the form", + "textLightColor": "Light Text Color", + "accentColor": "Accent", + "accentColorCaption": "Used for buttons and links", + "colorBackground": "Background", + "colorText": "Text", + "inputColor": "Input", + "inputColorCaption": "Used for email input", + "boxShadow": "Box Shadow", + "border": "Border", + "roundness": "Roundness", + "roundnessCaption": "Roundness of edges of the input, button, etc." + }, + "newsletter": { + "title": "Newsletter", + "name": "Newsletter Name", + "sendingEmail": "Sending email", + "subdomain": "Newsletter Subdomain", + "subdomainCaption": "Used for the archive site and the system sending email", + "delete": "Delete Newsletter", + "deleteContent": "Are you sure you want to delete this project? This action cannot be undone.", + "deleted": "Newsletter deleted" + }, + "sendingProfiles": { + "title": "Sending Profiles", + "sendingProfile": "Sending Profile", + "addSendingProfile": "Add Sending Profile", + "setAsDefault": "Set as Default Sending Profile", + "setAsDefaultContent": "Are you sure you want to set this sending profile as the default? This will be used to send all future issues unless changed.", + "deleteSendingProfile": "Delete Sending Profile", + "deleteSendingProfileContent": "Are you sure you want to delete this sending profile?", + "fromEmailRequired": "From email is required", + "fromEmail": "From Email", + "fromName": "From Name", + "replyToEmail": "Reply-To Email", + "brandName": "Brand Name", + "brandLogo": "Brand Logo", + "brandUrl": "Brand URL" + }, + "api": { + "apiKey": "API Key", + "title": "API Keys", + "deleteKey": "Are you sure you want to delete the API key {name}?", + "newKey": "Your new API key", + "warningNotice": "Important: This is the only time you'll see this API key. Make sure to copy it and store it securely.", + "name": "Name", + "nameCaption": "A descriptive name to identify this API key", + "namePlaceholder": "Enter API key name", + "key": "Key", + "keyCaption": "Invalidate old key and generate a new one", + "regenerate": "Regenerate", + "regenerateTitle": "Are you sure you want to regenerate the API Key", + "regenerateContent": "This will invalidate the old key and generate a new one. Make sure to update your existing keys with the new key.", + "regenerating": "Regenerating API key...", + "regenerateSuccess": "API key regenerated successfully", + "regenerateFailed": "Failed to regenerate API key", + "scopes": "Scopes", + "scopesCaption": "Select what actions this API key can perform", + "noKeys": "No API keys configured", + "nameRequired": "Name is required", + "nameCharLimit": "Name must be less than 255 characters", + "scopesRequired": "At least one scope is required", + "status": "Status", + "statusCaption": "Enable or disable this API key", + "enabled": "Enabled", + "disabled": "Disabled", + "failedAction": "Failed to {action} API key", + "create": "create", + "update": "update", + "lastUsed": "Last Used", + "neverUsed": "Never Used" + }, + "metadata": { + "metadata": "Metadata", + "subscriberMetadata": "Subscriber Metadata", + "key": "Key", + "keyCaption": "Uniquely identifies the metadata", + "keyValdation": "Only {characters} are allowed", + "displayName": "Display Name", + "displayNameCaption": "Shown in the Console and custom form fields", + "notFound": "No metadata found", + "add": "Add Metadata", + "update": "Update Metadata", + "delete": "Delete Metadata", + "deleteContent": "Are you sure you want to delete this metadata? This metadata will be removed from all subscribers. This action cannot be undone." + }, + "users": { + "title": "Users", + "add": "Add User", + "added": "User added", + "usernameEmailRequired": "Username or email is required", + "member": "Member", + "memberCaption": "Choose a member in your organization to add to this newsletter", + "removeAdminTitle": "Remove Admin", + "removeAdminContent": "Are you sure you want to remove this admin?", + "removeAdminSuccess": "User removed successfully" + } } - }, - "delete": { - "title": "Delete Issue", - "content": "Are you sure you want to delete this issue?", - "confirmText": "Yes, delete", - "success": "Issue deleted successfully" - } }, - "tools": { - "import": { - "title": "Import", - "status": { - "requires_input": "Requires Input", - "pending_approval": "Pending Approval", - "importing": "Importing", - "completed": "Completed", - "failed": "Failed" + "mail": { + "common": { + "greeting": "Hello {name} \uD83D\uDC4B,", + "regards": "Best regards," + }, + "userInvite": { + "subject": "You have been invited to join {newsletterName}", + "text": "You have been invited to join {newsletterName} as a {role}. Please click the button below to accept the invitation.", + "buttonText": "Accept Invitation", + "footerText": "Once you join, you will be able to access the Hyvor Post Console for this project using your HYVOR account." + }, + "domainVerification": { + "subject": "Your domain {domain} is verified", + "text": "Your domain {domain} is verified. You can now use it with your Hyvor Post projects." }, - "showFields": "Show Fields", - "mappedFields": "Mapped Fields", - "seeWarnings": "See Warnings", - "importWarnings": "Import Warnings", - "notMapped": "Not Mapped", - "importedCount": "Imported {count} subscribers" - }, - "export": { - "title": "Export", - "status": { - "pending": "Pending", - "completed": "Completed", - "failed": "Failed" + "subscriberConfirmation": { + "heading": "Confirm your subscription", + "buttonText": "Confirm Subscription" + }, + "approvalReviewing": { + "subject": "Your Hyvor Post approval request is under review" + }, + "approval": { + "subject": "Your Hyvor Post account has been {status}", + "bodyApproved": "We're happy to inform you that your Hyvor Post account has been approved. Now you can upgrade your account to a paid plan and start sending emails via Hyvor Post.", + "bodyRejected": "We regret to inform you that your Hyvor Post account approval request has been rejected.", + "buttonText": "Go to Console", + "footerText": "If you have any questions or need assistance, please don't hesitate to contact our support team by replying to this email.", + "note": "Note: {note}", + "reason": "Reject reason: {reason}" } - } - }, - "settings": { - "form": { - "signupForm": "Signup Form", - "texts": "Texts", - "textsHtmlAllowed": "HTML is allowed", - "title": "Title", - "titleCaption": "Shown above the form", - "description": "Description", - "descriptionCaption": "Shown below the title", - "footerText": "Footer text", - "footerTextCaption": "Shown below the form", - "subscribeButtonText": "Subscribe Button Text", - "subscribeButtonTextCaption": "Text shown on the subscribe button", - "successMessage": "Success Message", - "successMessageCaption": "Message shown after successful subscription", - "customCss": "Custom CSS", - "colorsUi": "Colors & UI", - "paletteLight": "Light Palette", - "paletteDark": "Dark Palette", - "textColor": "Text Color", - "textColorCaption": "Used for most texts in the form", - "textLightColor": "Light Text Color", - "accentColor": "Accent", - "accentColorCaption": "Used for buttons and links", - "colorBackground": "Background", - "colorText": "Text", - "inputColor": "Input", - "inputColorCaption": "Used for email input", - "boxShadow": "Box Shadow", - "border": "Border", - "roundness": "Roundness", - "roundnessCaption": "Roundness of edges of the input, button, etc." - }, - "newsletter": { - "title": "Newsletter", - "name": "Newsletter Name", - "sendingEmail": "Sending email", - "subdomain": "Newsletter Subdomain", - "subdomainCaption": "Used for the archive site and the system sending email", - "delete": "Delete Newsletter", - "deleteContent": "Are you sure you want to delete this project? This action cannot be undone.", - "deleted": "Newsletter deleted" - }, - "sendingProfiles": { - "title": "Sending Profiles", - "sendingProfile": "Sending Profile", - "addSendingProfile": "Add Sending Profile", - "setAsDefault": "Set as Default Sending Profile", - "setAsDefaultContent": "Are you sure you want to set this sending profile as the default? This will be used to send all future issues unless changed.", - "deleteSendingProfile": "Delete Sending Profile", - "deleteSendingProfileContent": "Are you sure you want to delete this sending profile?", - "fromEmailRequired": "From email is required", - "fromEmail": "From Email", - "fromName": "From Name", - "replyToEmail": "Reply-To Email", - "brandName": "Brand Name", - "brandLogo": "Brand Logo", - "brandUrl": "Brand URL" - }, - "api": { - "apiKey": "API Key", - "title": "API Keys", - "deleteKey": "Are you sure you want to delete the API key {name}?", - "newKey": "Your new API key", - "warningNotice": "Important: This is the only time you'll see this API key. Make sure to copy it and store it securely.", - "name": "Name", - "nameCaption": "A descriptive name to identify this API key", - "namePlaceholder": "Enter API key name", - "key": "Key", - "keyCaption": "Invalidate old key and generate a new one", - "regenerate": "Regenerate", - "regenerateTitle": "Are you sure you want to regenerate the API Key", - "regenerateContent": "This will invalidate the old key and generate a new one. Make sure to update your existing keys with the new key.", - "regenerating": "Regenerating API key...", - "regenerateSuccess": "API key regenerated successfully", - "regenerateFailed": "Failed to regenerate API key", - "scopes": "Scopes", - "scopesCaption": "Select what actions this API key can perform", - "noKeys": "No API keys configured", - "nameRequired": "Name is required", - "nameCharLimit": "Name must be less than 255 characters", - "scopesRequired": "At least one scope is required", - "status": "Status", - "statusCaption": "Enable or disable this API key", - "enabled": "Enabled", - "disabled": "Disabled", - "failedAction": "Failed to {action} API key", - "create": "create", - "update": "update", - "lastUsed": "Last Used", - "neverUsed": "Never Used" - }, - "metadata": { - "metadata": "Metadata", - "subscriberMetadata": "Subscriber Metadata", - "key": "Key", - "keyCaption": "Uniquely identifies the metadata", - "keyValdation": "Only {characters} are allowed", - "displayName": "Display Name", - "displayNameCaption": "Shown in the Console and custom form fields", - "notFound": "No metadata found", - "add": "Add Metadata", - "update": "Update Metadata", - "delete": "Delete Metadata", - "deleteContent": "Are you sure you want to delete this metadata? This metadata will be removed from all subscribers. This action cannot be undone." - }, - "users": { - "title": "Users", - "add": "Add User", - "invite": "Invite", - "inviteUser": "Invite User", - "inviteNewAdmin": "Invite New Admin", - "usernameEmailRequired": "Username or email is required", - "inviteSent": "Invitation sent", - "hyvorAccRequired": "Hyvor Account Required", - "askAdmin": "Ask your admin to create a HYVOR account before inviting.", - "username": "Username or Email", - "usernameCaption": "Username or email of the admin's HYVOR account", - "waitBeforeResend": "Please wait a minute before resending an invite", - "resendInviteSuccess": "Invitation resent", - "deleteInviteTitle": "Delete Invite", - "deleteInviteContent": "Are you sure you want to delete this invite?", - "removeAdminTitle": "Remove Admin", - "removeAdminContent": "Are you sure you want to remove this admin?", - "removeAdminSuccess": "User removed successfully" - } - } - }, - "mail": { - "common": { - "greeting": "Hello {name} \uD83D\uDC4B,", - "regards": "Best regards," - }, - "userInvite": { - "subject": "You have been invited to join {newsletterName}", - "text": "You have been invited to join {newsletterName} as a {role}. Please click the button below to accept the invitation.", - "buttonText": "Accept Invitation", - "footerText": "Once you join, you will be able to access the Hyvor Post Console for this project using your HYVOR account." - }, - "domainVerification": { - "subject": "Your domain {domain} is verified", - "text": "Your domain {domain} is verified. You can now use it with your Hyvor Post projects." - }, - "subscriberConfirmation": { - "heading": "Confirm your subscription", - "buttonText": "Confirm Subscription" - }, - "approvalReviewing": { - "subject": "Your Hyvor Post approval request is under review" - }, - "approval": { - "subject": "Your Hyvor Post account has been {status}", - "bodyApproved": "We're happy to inform you that your Hyvor Post account has been approved. Now you can upgrade your account to a paid plan and start sending emails via Hyvor Post.", - "bodyRejected": "We regret to inform you that your Hyvor Post account approval request has been rejected.", - "buttonText": "Go to Console", - "footerText": "If you have any questions or need assistance, please don't hesitate to contact our support team by replying to this email.", - "note": "Note: {note}", - "reason": "Reject reason: {reason}" } - } } From cb8c58fb7eaab824be0229a3907514daaeabf6e8 Mon Sep 17 00:00:00 2001 From: Supun Wimalasena Date: Mon, 23 Feb 2026 12:24:26 +0100 Subject: [PATCH 3/3] test fix --- backend/tests/Api/Console/User/CreateUserTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/tests/Api/Console/User/CreateUserTest.php b/backend/tests/Api/Console/User/CreateUserTest.php index 2ab4abf5..85e869b6 100644 --- a/backend/tests/Api/Console/User/CreateUserTest.php +++ b/backend/tests/Api/Console/User/CreateUserTest.php @@ -62,7 +62,7 @@ public function test_when_already_an_admin(): void ] ); - $this->assertResponseFailed(400, "User is already an admin"); + $this->assertResponseFailed(400, "User is already added to the newsletter"); } public function test_when_not_an_organization_member(): void @@ -104,4 +104,4 @@ public function test_when_comms_api_fail(): void $this->assertResponseFailed(400, 'Unable to verify the user. Please try again later.'); } -} \ No newline at end of file +}