diff --git a/backend/src/common/types.ts b/backend/src/common/types.ts index dabdae25..9c0ca79c 100644 --- a/backend/src/common/types.ts +++ b/backend/src/common/types.ts @@ -141,12 +141,12 @@ export interface FullUser extends User { canDeleteSession: boolean; stripeId: string | null; pro: boolean; - subscriptionsId: string | null; currency: Currency | null; plan: Plan | null; planOwner: string | null; planOwnerEmail: string | null; planAdmins: string[] | null; + planMembers: string[] | null; domain: string | null; ownPlan: Plan | null; ownSubscriptionsId: string | null; diff --git a/backend/src/db/entities/UserView.ts b/backend/src/db/entities/UserView.ts index d0666c65..1de05a10 100644 --- a/backend/src/db/entities/UserView.ts +++ b/backend/src/db/entities/UserView.ts @@ -3,7 +3,7 @@ import { AccountType, FullUser, Currency, Plan } from '../../common/index.js'; @ViewEntity({ expression: ` - select +select u.id, i.id as identity_id, u.name, @@ -18,13 +18,13 @@ import { AccountType, FullUser, Currency, Plan } from '../../common/index.js'; u.trial, s1.id as "own_subscriptions_id", s1.plan as "own_plan", - coalesce(s1.id, s2.id, s3.id) as "subscriptions_id", coalesce(s1.active, s2.active, s3.active, false) as "pro", /* s4 should not be taken into account for Pro */ coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as "plan", coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as "domain", coalesce(o1.name, o2.name, o3.name, o4.name) as "plan_owner", coalesce(o1.email, o2.email, o3.email, o4.email) as "plan_owner_email", - coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins" + coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins", + coalesce(s1.members, s2.members, s3.members, s4.members) as "plan_members" from users_identities i join users u on u.id = i.user_id @@ -70,7 +70,7 @@ export default class UserView { @ViewColumn() public planAdmins: string[] | null; @ViewColumn() - public subscriptionsId: string | null; + public planMembers: string[] | null; @ViewColumn() public plan: Plan | null; @ViewColumn() @@ -91,7 +91,6 @@ export default class UserView { this.username = null; this.photo = null; this.stripeId = null; - this.subscriptionsId = null; this.pro = false; this.email = null; this.canDeleteSession = false; @@ -100,6 +99,7 @@ export default class UserView { this.planOwner = null; this.planOwnerEmail = null; this.planAdmins = null; + this.planMembers = null; this.ownSubscriptionsId = null; this.plan = null; this.domain = null; @@ -115,7 +115,6 @@ export default class UserView { email: this.email, canDeleteSession: this.canDeleteSession, pro: this.pro, - subscriptionsId: this.subscriptionsId, accountType: this.accountType, language: this.language, username: this.username, @@ -125,6 +124,7 @@ export default class UserView { planOwner: this.planOwner, planOwnerEmail: this.planOwnerEmail, planAdmins: this.planAdmins, + planMembers: this.planMembers, domain: this.domain, ownPlan: this.ownPlan, ownSubscriptionsId: this.ownSubscriptionsId, diff --git a/backend/src/db/migrations/1684682842078-AddMembersToView.ts b/backend/src/db/migrations/1684682842078-AddMembersToView.ts new file mode 100644 index 00000000..b1a09812 --- /dev/null +++ b/backend/src/db/migrations/1684682842078-AddMembersToView.ts @@ -0,0 +1,87 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddMembersToView1684682842078 implements MigrationInterface { + name = 'AddMembersToView1684682842078' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","user_view","public"]); + await queryRunner.query(`DROP VIEW "user_view"`); + await queryRunner.query(`CREATE VIEW "user_view" AS +select + u.id, + i.id as identity_id, + u.name, + i.account_type, + i.username, + u.currency, + u.stripe_id, + i.photo, + u.language, + u.email, + case when i.account_type = 'anonymous' and i.password is null then false else true end as "can_delete_session", + u.trial, + s1.id as "own_subscriptions_id", + s1.plan as "own_plan", + coalesce(s1.id, s2.id, s3.id) as "subscriptions_id", + coalesce(s1.active, s2.active, s3.active, false) as "pro", /* s4 should not be taken into account for Pro */ + coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as "plan", + coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as "domain", + coalesce(o1.name, o2.name, o3.name, o4.name) as "plan_owner", + coalesce(o1.email, o2.email, o3.email, o4.email) as "plan_owner_email", + coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins", + coalesce(s1.members, s2.members, s3.members, s4.members) as "plan_members" +from users_identities i + +join users u on u.id = i.user_id +left join subscriptions s1 on s1.owner_id = u.id and s1.active is true +left join users o1 on o1.id = s1.owner_id +left join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true +left join users o2 on o2.id = s2.owner_id +left join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true +left join users o3 on o3.id = s3.owner_id +left join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true +left join users o4 on o4.id = s4.owner_id + `); + await queryRunner.query(`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["public","VIEW","user_view","select \n u.id,\n i.id as identity_id,\n u.name,\n i.account_type,\n i.username,\n u.currency,\n u.stripe_id,\n i.photo,\n u.language,\n u.email,\n case when i.account_type = 'anonymous' and i.password is null then false else true end as \"can_delete_session\",\n u.trial,\n s1.id as \"own_subscriptions_id\",\n s1.plan as \"own_plan\",\n coalesce(s1.id, s2.id, s3.id) as \"subscriptions_id\",\n coalesce(s1.active, s2.active, s3.active, false) as \"pro\", /* s4 should not be taken into account for Pro */\n coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as \"plan\",\n coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as \"domain\",\n coalesce(o1.name, o2.name, o3.name, o4.name) as \"plan_owner\",\n coalesce(o1.email, o2.email, o3.email, o4.email) as \"plan_owner_email\",\n coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as \"plan_admins\",\n coalesce(s1.members, s2.members, s3.members, s4.members) as \"plan_members\"\nfrom users_identities i\n\njoin users u on u.id = i.user_id\nleft join subscriptions s1 on s1.owner_id = u.id and s1.active is true\nleft join users o1 on o1.id = s1.owner_id\nleft join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true\nleft join users o2 on o2.id = s2.owner_id\nleft join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true\nleft join users o3 on o3.id = s3.owner_id\nleft join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true\nleft join users o4 on o4.id = s4.owner_id"]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","user_view","public"]); + await queryRunner.query(`DROP VIEW "user_view"`); + await queryRunner.query(`CREATE VIEW "user_view" AS select + u.id, + i.id as identity_id, + u.name, + i.account_type, + i.username, + u.currency, + u.stripe_id, + i.photo, + u.language, + u.email, + case when i.account_type = 'anonymous' and i.password is null then false else true end as "can_delete_session", + u.trial, + s1.id as "own_subscriptions_id", + s1.plan as "own_plan", + coalesce(s1.id, s2.id, s3.id) as "subscriptions_id", + coalesce(s1.active, s2.active, s3.active, false) as "pro", /* s4 should not be taken into account for Pro */ + coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as "plan", + coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as "domain", + coalesce(o1.name, o2.name, o3.name, o4.name) as "plan_owner", + coalesce(o1.email, o2.email, o3.email, o4.email) as "plan_owner_email", + coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins" +from users_identities i + +join users u on u.id = i.user_id +left join subscriptions s1 on s1.owner_id = u.id and s1.active is true +left join users o1 on o1.id = s1.owner_id +left join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true +left join users o2 on o2.id = s2.owner_id +left join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true +left join users o3 on o3.id = s3.owner_id +left join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true +left join users o4 on o4.id = s4.owner_id`); + await queryRunner.query(`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["public","VIEW","user_view","select \n u.id,\n i.id as identity_id,\n u.name,\n i.account_type,\n i.username,\n u.currency,\n u.stripe_id,\n i.photo,\n u.language,\n u.email,\n case when i.account_type = 'anonymous' and i.password is null then false else true end as \"can_delete_session\",\n u.trial,\n s1.id as \"own_subscriptions_id\",\n s1.plan as \"own_plan\",\n coalesce(s1.id, s2.id, s3.id) as \"subscriptions_id\",\n coalesce(s1.active, s2.active, s3.active, false) as \"pro\", /* s4 should not be taken into account for Pro */\n coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as \"plan\",\n coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as \"domain\",\n coalesce(o1.name, o2.name, o3.name, o4.name) as \"plan_owner\",\n coalesce(o1.email, o2.email, o3.email, o4.email) as \"plan_owner_email\",\n coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as \"plan_admins\"\nfrom users_identities i\n\njoin users u on u.id = i.user_id\nleft join subscriptions s1 on s1.owner_id = u.id and s1.active is true\nleft join users o1 on o1.id = s1.owner_id\nleft join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true\nleft join users o2 on o2.id = s2.owner_id\nleft join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true\nleft join users o3 on o3.id = s3.owner_id\nleft join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true\nleft join users o4 on o4.id = s4.owner_id"]); + } + +} diff --git a/backend/src/db/migrations/1684684514088-DontLeakSubId.ts b/backend/src/db/migrations/1684684514088-DontLeakSubId.ts new file mode 100644 index 00000000..d0411cac --- /dev/null +++ b/backend/src/db/migrations/1684684514088-DontLeakSubId.ts @@ -0,0 +1,87 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class DontLeakSubId1684684514088 implements MigrationInterface { + name = 'DontLeakSubId1684684514088' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","user_view","public"]); + await queryRunner.query(`DROP VIEW "user_view"`); + await queryRunner.query(`CREATE VIEW "user_view" AS +select + u.id, + i.id as identity_id, + u.name, + i.account_type, + i.username, + u.currency, + u.stripe_id, + i.photo, + u.language, + u.email, + case when i.account_type = 'anonymous' and i.password is null then false else true end as "can_delete_session", + u.trial, + s1.id as "own_subscriptions_id", + s1.plan as "own_plan", + coalesce(s1.active, s2.active, s3.active, false) as "pro", /* s4 should not be taken into account for Pro */ + coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as "plan", + coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as "domain", + coalesce(o1.name, o2.name, o3.name, o4.name) as "plan_owner", + coalesce(o1.email, o2.email, o3.email, o4.email) as "plan_owner_email", + coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins", + coalesce(s1.members, s2.members, s3.members, s4.members) as "plan_members" +from users_identities i + +join users u on u.id = i.user_id +left join subscriptions s1 on s1.owner_id = u.id and s1.active is true +left join users o1 on o1.id = s1.owner_id +left join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true +left join users o2 on o2.id = s2.owner_id +left join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true +left join users o3 on o3.id = s3.owner_id +left join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true +left join users o4 on o4.id = s4.owner_id + `); + await queryRunner.query(`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["public","VIEW","user_view","select \n u.id,\n i.id as identity_id,\n u.name,\n i.account_type,\n i.username,\n u.currency,\n u.stripe_id,\n i.photo,\n u.language,\n u.email,\n case when i.account_type = 'anonymous' and i.password is null then false else true end as \"can_delete_session\",\n u.trial,\n s1.id as \"own_subscriptions_id\",\n s1.plan as \"own_plan\",\n coalesce(s1.active, s2.active, s3.active, false) as \"pro\", /* s4 should not be taken into account for Pro */\n coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as \"plan\",\n coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as \"domain\",\n coalesce(o1.name, o2.name, o3.name, o4.name) as \"plan_owner\",\n coalesce(o1.email, o2.email, o3.email, o4.email) as \"plan_owner_email\",\n coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as \"plan_admins\",\n coalesce(s1.members, s2.members, s3.members, s4.members) as \"plan_members\"\nfrom users_identities i\n\njoin users u on u.id = i.user_id\nleft join subscriptions s1 on s1.owner_id = u.id and s1.active is true\nleft join users o1 on o1.id = s1.owner_id\nleft join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true\nleft join users o2 on o2.id = s2.owner_id\nleft join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true\nleft join users o3 on o3.id = s3.owner_id\nleft join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true\nleft join users o4 on o4.id = s4.owner_id"]); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DELETE FROM "typeorm_metadata" WHERE "type" = $1 AND "name" = $2 AND "schema" = $3`, ["VIEW","user_view","public"]); + await queryRunner.query(`DROP VIEW "user_view"`); + await queryRunner.query(`CREATE VIEW "user_view" AS select + u.id, + i.id as identity_id, + u.name, + i.account_type, + i.username, + u.currency, + u.stripe_id, + i.photo, + u.language, + u.email, + case when i.account_type = 'anonymous' and i.password is null then false else true end as "can_delete_session", + u.trial, + s1.id as "own_subscriptions_id", + s1.plan as "own_plan", + coalesce(s1.id, s2.id, s3.id) as "subscriptions_id", + coalesce(s1.active, s2.active, s3.active, false) as "pro", /* s4 should not be taken into account for Pro */ + coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as "plan", + coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as "domain", + coalesce(o1.name, o2.name, o3.name, o4.name) as "plan_owner", + coalesce(o1.email, o2.email, o3.email, o4.email) as "plan_owner_email", + coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as "plan_admins", + coalesce(s1.members, s2.members, s3.members, s4.members) as "plan_members" +from users_identities i + +join users u on u.id = i.user_id +left join subscriptions s1 on s1.owner_id = u.id and s1.active is true +left join users o1 on o1.id = s1.owner_id +left join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true +left join users o2 on o2.id = s2.owner_id +left join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true +left join users o3 on o3.id = s3.owner_id +left join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true +left join users o4 on o4.id = s4.owner_id`); + await queryRunner.query(`INSERT INTO "typeorm_metadata"("database", "schema", "table", "type", "name", "value") VALUES (DEFAULT, $1, DEFAULT, $2, $3, $4)`, ["public","VIEW","user_view","select \n u.id,\n i.id as identity_id,\n u.name,\n i.account_type,\n i.username,\n u.currency,\n u.stripe_id,\n i.photo,\n u.language,\n u.email,\n case when i.account_type = 'anonymous' and i.password is null then false else true end as \"can_delete_session\",\n u.trial,\n s1.id as \"own_subscriptions_id\",\n s1.plan as \"own_plan\",\n coalesce(s1.id, s2.id, s3.id) as \"subscriptions_id\",\n coalesce(s1.active, s2.active, s3.active, false) as \"pro\", /* s4 should not be taken into account for Pro */\n coalesce(s1.plan, s2.plan, s3.plan, s4.plan) as \"plan\",\n coalesce(s1.domain, s2.domain, s3.domain, s4.domain) as \"domain\",\n coalesce(o1.name, o2.name, o3.name, o4.name) as \"plan_owner\",\n coalesce(o1.email, o2.email, o3.email, o4.email) as \"plan_owner_email\",\n coalesce(s1.admins, s2.admins, s3.admins, s4.admins) as \"plan_admins\",\n coalesce(s1.members, s2.members, s3.members, s4.members) as \"plan_members\"\nfrom users_identities i\n\njoin users u on u.id = i.user_id\nleft join subscriptions s1 on s1.owner_id = u.id and s1.active is true\nleft join users o1 on o1.id = s1.owner_id\nleft join subscriptions s2 on s2.members @> ARRAY[u.email::text] and s2.active is true\nleft join users o2 on o2.id = s2.owner_id\nleft join subscriptions s3 on s3.domain = split_part(u.email, '@', 2) and s3.active is true\nleft join users o3 on o3.id = s3.owner_id\nleft join subscriptions s4 on s4.admins @> ARRAY[u.email::text] and s4.active is true\nleft join users o4 on o4.id = s4.owner_id"]); + } + +} diff --git a/frontend/package.json b/frontend/package.json index ec89d315..cb590857 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -96,7 +96,8 @@ "lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0", "test": "vitest", "ci-test": "CI=true yarn test", - "analyze": "source-map-explorer build/static/js/*" + "analyze": "source-map-explorer build/static/js/*", + "watch": "tsc --watch --noEmit" }, "eslintConfig": { "extends": "react-app" diff --git a/frontend/src/common/types.ts b/frontend/src/common/types.ts index dabdae25..9c0ca79c 100644 --- a/frontend/src/common/types.ts +++ b/frontend/src/common/types.ts @@ -141,12 +141,12 @@ export interface FullUser extends User { canDeleteSession: boolean; stripeId: string | null; pro: boolean; - subscriptionsId: string | null; currency: Currency | null; plan: Plan | null; planOwner: string | null; planOwnerEmail: string | null; planAdmins: string[] | null; + planMembers: string[] | null; domain: string | null; ownPlan: Plan | null; ownSubscriptionsId: string | null; diff --git a/frontend/src/components/ClosableAlert.tsx b/frontend/src/components/ClosableAlert.tsx index 017e0aa9..13bfd8ce 100644 --- a/frontend/src/components/ClosableAlert.tsx +++ b/frontend/src/components/ClosableAlert.tsx @@ -1,11 +1,13 @@ import { Alert, AlertColor, AlertTitle } from '@mui/material'; -import { useState } from 'react'; +import { useState, useCallback } from 'react'; type ClosableAlertProps = { title?: React.ReactNode; children: React.ReactNode; severity: AlertColor; closable?: boolean; + id?: string; + persisted?: boolean; }; export default function ClosableAlert({ @@ -13,18 +15,42 @@ export default function ClosableAlert({ children, severity, closable, + id, + persisted, }: ClosableAlertProps) { - const [open, setOpen] = useState(true); + const [open, setOpen] = useState(loadInitial(id)); + + const handleClose = useCallback(() => { + if (!closable) return; + setOpen(false); + if (persisted && id) { + localStorage.setItem(computeKey(id), 'done'); + } + }, [id, persisted, closable]); + + if (persisted && !id) { + throw Error('ClosableAlert with persisted=true must have an id'); + } if (!open) return null; return ( - setOpen(false) : undefined} - > + {title} {children} ); } + +function computeKey(id: string) { + return 'closable-alert-' + id; +} + +function loadInitial(id?: string) { + if (!id) return true; + const initial = localStorage.getItem(computeKey(id)); + if (initial) { + return initial !== 'done'; + } + return true; +} diff --git a/frontend/src/testing/index.tsx b/frontend/src/testing/index.tsx index 940f57ce..947832ec 100644 --- a/frontend/src/testing/index.tsx +++ b/frontend/src/testing/index.tsx @@ -21,12 +21,12 @@ const user: FullUser = { email: 'john@doe.com', pro: false, stripeId: null, - subscriptionsId: null, currency: null, plan: null, planOwner: null, planOwnerEmail: null, planAdmins: null, + planMembers: null, domain: null, ownPlan: null, ownSubscriptionsId: null, diff --git a/frontend/src/translations/locales/ar-SA.json b/frontend/src/translations/locales/ar-SA.json index e5feca2d..0d83a22b 100644 --- a/frontend/src/translations/locales/ar-SA.json +++ b/frontend/src/translations/locales/ar-SA.json @@ -27,7 +27,9 @@ "anonWarning": "أنت تستخدم حساب مجهول. قد ترغب في تسجيل الدخول للتأكد من أن البيانات الخاصة بك يمكن استرجاعها عند استخدام جهاز آخر.", "howDoesThatWork": "كيف يعمل ذلك؟", "login": "تسجيل الدخول", - "searchNoMatch": "لم نتمكن من العثور على رجعي يطابق بحثك ({{search}})." + "searchNoMatch": "لم نتمكن من العثور على رجعي يطابق بحثك ({{search}}).", + "proNotSetupWarning": "لقد اشتركت في حساب فريق المحترفين، مما يعني أنه يمكنك منح 19 زميلاً آخر حساب محترف. لإضافتهم إلى اشتراكك حتى يمكنهم أن يصبحوا برو أيضا، الرجاء الذهاب إلى صفحة حسابك وإضافة رسائل البريد الإلكتروني الخاصة بهم (قسم \"فريقك\").", + "proNotSetupWarningAction": "الذهاب إلى صفحة الحساب" }, "PreviousGame": { "createdBy": "تم إنشاؤها بواسطة", @@ -324,6 +326,7 @@ "header": "خطتك", "plan": "خطة", "admins": "المدراء", + "members": "الأعضاء", "youAreOwner": "أنت صاحب هذه الخطة، من خلال الاشتراك أدناه.", "youAreMember": "أنت على هذه الخطة من خلال اشتراك شخص آخر.", "ownedBy": "تملكها من قبل", @@ -510,4 +513,4 @@ ], "feedback": "ترك بعض الملاحظات" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/de-DE.json b/frontend/src/translations/locales/de-DE.json index 760fc52a..7800bfd5 100644 --- a/frontend/src/translations/locales/de-DE.json +++ b/frontend/src/translations/locales/de-DE.json @@ -27,7 +27,9 @@ "anonWarning": "Du verwendest ein anonymes Konto. Du solltest dich einloggen, um sicherzustellen, dass deine Daten abgerufen werden können, wenn du ein anderes Gerät verwendest.", "howDoesThatWork": "Wie funktioniert das?", "login": "Anmelden", - "searchNoMatch": "Wir konnten keine Retrospektive finden, die Ihrer Suche entspricht ({{search}})." + "searchNoMatch": "Wir konnten keine Retrospektive finden, die Ihrer Suche entspricht ({{search}}).", + "proNotSetupWarning": "Sie haben ein Pro-Team-Konto abonniert, was bedeutet, dass Sie 19 anderen Kollegen ein Pro-Konto gewähren können. Um sie Ihrem Abonnement hinzuzufügen, damit sie auch Pro werden können gehen Sie bitte auf Ihre Account-Seite und fügen Sie ihre E-Mails hinzu ('Ihr Team').", + "proNotSetupWarningAction": "Gehe zur Kontoseite" }, "PreviousGame": { "createdBy": "Erstellt von", @@ -324,6 +326,7 @@ "header": "Ihr Plan", "plan": "Plan", "admins": "Administratoren", + "members": "Mitglieder", "youAreOwner": "Sie sind der Besitzer dieses Pakets, durch das untenstehende Abonnement.", "youAreMember": "Du bist auf diesem Plan durch ein anderes Abonnement.", "ownedBy": "Besitzt von", @@ -510,4 +513,4 @@ ], "feedback": "Feedback hinterlassen" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/en-GB.json b/frontend/src/translations/locales/en-GB.json index e90fd635..8f926013 100644 --- a/frontend/src/translations/locales/en-GB.json +++ b/frontend/src/translations/locales/en-GB.json @@ -27,7 +27,9 @@ "anonWarning": "You are using an Anonymous account. You might want to login to make sure your data can be retrieved when using another device.", "howDoesThatWork": "How does that work?", "login": "Login", - "searchNoMatch": "We could not find a retrospective matching your search ({{search}})." + "searchNoMatch": "We could not find a retrospective matching your search ({{search}}).", + "proNotSetupWarning": "You have subscribed to a Pro Team account, which means you can grant 19 other colleagues a Pro account. To add them to your subscription so they can become Pro as well, please go to your Account page and add their emails ('Your Team' section).", + "proNotSetupWarningAction": "Go to Account page" }, "PreviousGame": { "createdBy": "Created by", @@ -160,7 +162,7 @@ "userIsReady": "{{user}} is ready!", "deleteConfirmation": { "title": "Delete this post?", - "description":"This will delete the post and all its votes. This action cannot be undone.", + "description": "This will delete the post and all its votes. This action cannot be undone.", "confirm": "Delete this post", "cancel": "I have changed my mind" }, @@ -221,13 +223,7 @@ "nameField": "Your name (for display purposes)", "noAuthWarning": "Your administrator disabled all login possibilities (OAuth, password). Ask your administrator to re-enable at least one.", "or": "or", - "passwordScoreWords": [ - "weak", - "weak", - "not quite", - "good", - "strong" - ], + "passwordScoreWords": ["weak", "weak", "not quite", "good", "strong"], "skipAndAnonLogin": "Use Anonymously" }, "AccountLogin": { @@ -324,6 +320,7 @@ "header": "Your Plan", "plan": "Plan", "admins": "Administrators", + "members": "Members", "youAreOwner": "You are the owner of this plan, through the subscription below.", "youAreMember": "You are on this plan through somebody else's subscription.", "ownedBy": "Owned By", @@ -510,4 +507,4 @@ ], "feedback": "Leave some feedback" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/es-ES.json b/frontend/src/translations/locales/es-ES.json index 499aa183..f02a6b9d 100644 --- a/frontend/src/translations/locales/es-ES.json +++ b/frontend/src/translations/locales/es-ES.json @@ -27,7 +27,9 @@ "anonWarning": "Estás usando una cuenta anónima. Tal vez quieras iniciar sesión para asegurarte de que tus datos pueden ser recuperados cuando usas otro dispositivo.", "howDoesThatWork": "¿Cómo funciona esto?", "login": "Ingresar", - "searchNoMatch": "No hemos podido encontrar un retrospectivo que coincida con tu búsqueda ({{search}})." + "searchNoMatch": "No hemos podido encontrar un retrospectivo que coincida con tu búsqueda ({{search}}).", + "proNotSetupWarning": "Te has suscrito a una cuenta Pro Team, lo que significa que puedes conceder a otros 19 colegas una cuenta Pro. Para añadirlos a tu suscripción para que también puedan convertirse en Pro por favor vaya a la página de su cuenta y añada sus correos electrónicos ('Su equipo').", + "proNotSetupWarningAction": "Ir a la página de cuenta" }, "PreviousGame": { "createdBy": "Creado por", @@ -324,6 +326,7 @@ "header": "Su plan", "plan": "Plano", "admins": "Administradores", + "members": "Miembros", "youAreOwner": "Usted es el propietario de este plan, a través de la suscripción a continuación.", "youAreMember": "Estás en este plan a través de la suscripción de otra persona.", "ownedBy": "Poseído por", @@ -510,4 +513,4 @@ ], "feedback": "Dejar algunos comentarios" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/fr-FR.json b/frontend/src/translations/locales/fr-FR.json index 5b9f24c1..48717fe7 100644 --- a/frontend/src/translations/locales/fr-FR.json +++ b/frontend/src/translations/locales/fr-FR.json @@ -27,7 +27,9 @@ "anonWarning": "Vous utilisez un compte anonyme. Connectez-vous afin que vos données puissent être récupérées lorsque vous utilisez un autre appareil.", "howDoesThatWork": "Comment cela fonctionne-t-il ?", "login": "Connexion", - "searchNoMatch": "Nous n'avons pu trouver une retrospective correspondant à votre recherche ({{search}})." + "searchNoMatch": "Nous n'avons pu trouver une retrospective correspondant à votre recherche ({{search}}).", + "proNotSetupWarning": "Vous avez souscrit à un compte Pro Team, ce qui signifie que vous pouvez accorder à 19 autres collègues un compte Pro. Pour les ajouter à votre abonnement afin qu'ils puissent bénéficier de ses avantages, veuillez vous rendre sur la page \"Mon compte\" et ajouter leurs e-mails dans la section « Votre équipe ».", + "proNotSetupWarningAction": "Aller à la page \"Mon compte\"" }, "PreviousGame": { "createdBy": "Créé par", @@ -324,6 +326,7 @@ "header": "Votre Accès", "plan": "Accès", "admins": "Administrateurs", + "members": "Membres", "youAreOwner": "Vous êtes l'administrateur de cet abonnement. Vous pouvez le gérer via la section ci-dessous.", "youAreMember": "Vous devez votre accès Pro grâce à l'abonnement d'un tiers.", "ownedBy": "Détenu par", @@ -510,4 +513,4 @@ ], "feedback": "Donnez-nous votre avis !" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/hu-HU.json b/frontend/src/translations/locales/hu-HU.json index 4350131e..c7a95759 100644 --- a/frontend/src/translations/locales/hu-HU.json +++ b/frontend/src/translations/locales/hu-HU.json @@ -27,7 +27,9 @@ "anonWarning": "", "howDoesThatWork": "Hogyan működik?", "login": "", - "searchNoMatch": "" + "searchNoMatch": "", + "proNotSetupWarning": "", + "proNotSetupWarningAction": "" }, "PreviousGame": { "createdBy": "Készítette", @@ -324,6 +326,7 @@ "header": "Az Ön terve", "plan": "Terv", "admins": "", + "members": "", "youAreOwner": "Az alábbi előfizetésen keresztül Ön ennek a csomagnak a tulajdonosa.", "youAreMember": "Valaki más előfizetésén keresztül részt vesz ebben a tervben.", "ownedBy": "", @@ -510,4 +513,4 @@ ], "feedback": "" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/it-IT.json b/frontend/src/translations/locales/it-IT.json index 3e7498f9..a2240e83 100644 --- a/frontend/src/translations/locales/it-IT.json +++ b/frontend/src/translations/locales/it-IT.json @@ -27,7 +27,9 @@ "anonWarning": "Stai utilizzando un account anonimo. Potresti voler effettuare il login per assicurarsi che i tuoi dati possano essere recuperati quando usi un altro dispositivo.", "howDoesThatWork": "Come funziona quello?", "login": "Accedi", - "searchNoMatch": "Non siamo riusciti a trovare una retrospettiva corrispondente alla tua ricerca ({{search}})." + "searchNoMatch": "Non siamo riusciti a trovare una retrospettiva corrispondente alla tua ricerca ({{search}}).", + "proNotSetupWarning": "Hai sottoscritto un account Pro Team, il che significa che puoi concedere ad altri 19 colleghi un account Pro. Per aggiungerli al tuo abbonamento in modo che possano diventare anche Pro, vai alla pagina del tuo Account e aggiungi le loro email (sezione 'Il tuo Team').", + "proNotSetupWarningAction": "Vai alla pagina Account" }, "PreviousGame": { "createdBy": "Creato da", @@ -324,6 +326,7 @@ "header": "Il Tuo Piano", "plan": "Piano", "admins": "Amministratori", + "members": "Membri", "youAreOwner": "Sei il proprietario di questo piano, tramite l'abbonamento qui sotto.", "youAreMember": "Sei su questo piano attraverso l'abbonamento di qualcun altro.", "ownedBy": "Posseduta Da", @@ -510,4 +513,4 @@ ], "feedback": "Lascia qualche feedback" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/ja-JP.json b/frontend/src/translations/locales/ja-JP.json index 48170484..d7e36763 100644 --- a/frontend/src/translations/locales/ja-JP.json +++ b/frontend/src/translations/locales/ja-JP.json @@ -27,7 +27,9 @@ "anonWarning": "匿名アカウントを使用しています。別のデバイスを使用しているときにデータを取得できることを確認するためにログインする場合があります。", "howDoesThatWork": "それはどのように機能しますか?", "login": "ログイン", - "searchNoMatch": "検索に一致する回顧展が見つかりませんでした ({{search}})。" + "searchNoMatch": "検索に一致する回顧展が見つかりませんでした ({{search}})。", + "proNotSetupWarning": "Proチームアカウントを購読しているため、19人の同僚にProアカウントを付与することができます。 サブスクリプションに追加することで、Pro版にもなります。 アカウントページに行き、メールアドレス(「チーム」セクション)を追加してください。", + "proNotSetupWarningAction": "アカウントページに移動" }, "PreviousGame": { "createdBy": "作成者:", @@ -324,6 +326,7 @@ "header": "あなたのプラン", "plan": "プラン", "admins": "管理者", + "members": "メンバー", "youAreOwner": "あなたは以下のサブスクリプションを通じて、このプランの所有者です。", "youAreMember": "あなたは他人のサブスクリプションを通じてこのプランにいます。", "ownedBy": "所有者:", @@ -510,4 +513,4 @@ ], "feedback": "いくつかのフィードバックを残す" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/nl-NL.json b/frontend/src/translations/locales/nl-NL.json index 8c9da187..a1358baa 100644 --- a/frontend/src/translations/locales/nl-NL.json +++ b/frontend/src/translations/locales/nl-NL.json @@ -27,7 +27,9 @@ "anonWarning": "Je gebruikt een anonieme account. Je kunt misschien inloggen om ervoor te zorgen dat je gegevens kunnen worden opgehaald wanneer je een ander apparaat gebruikt.", "howDoesThatWork": "Hoe werkt dat?", "login": "Aanmelden", - "searchNoMatch": "We konden geen terugwerkende kracht vinden die overeenkomt met uw zoekopdracht ({{search}})." + "searchNoMatch": "We konden geen terugwerkende kracht vinden die overeenkomt met uw zoekopdracht ({{search}}).", + "proNotSetupWarning": "U heeft zich geabonneerd op een Pro-teamaccount, wat betekent dat u 19 andere collega's een Pro-account kunt geven. Om ze toe te voegen aan uw abonnement zodat ze ook Pro kunnen worden, Ga naar uw accountpagina en voeg hun e-mails toe ('Uw team' sectie).", + "proNotSetupWarningAction": "Ga naar Account pagina" }, "PreviousGame": { "createdBy": "Aangemaakt door", @@ -324,6 +326,7 @@ "header": "Uw abonnement", "plan": "Abonnement", "admins": "Beheerders", + "members": "leden", "youAreOwner": "U bent de eigenaar van dit abonnement, via het onderstaande abonnement.", "youAreMember": "U bent op dit plan via iemands abonnement.", "ownedBy": "Eigendom van", @@ -510,4 +513,4 @@ ], "feedback": "Laat feedback achter" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/pl-PL.json b/frontend/src/translations/locales/pl-PL.json index 61e8439e..8c9b6ac5 100644 --- a/frontend/src/translations/locales/pl-PL.json +++ b/frontend/src/translations/locales/pl-PL.json @@ -27,7 +27,9 @@ "anonWarning": "Używasz konta anonimowego. Możesz się zalogować, aby mieć pewność, że dane mogą być pobrane podczas korzystania z innego urządzenia.", "howDoesThatWork": "Jak to działa?", "login": "Logowanie", - "searchNoMatch": "Nie mogliśmy znaleźć retrospektywnego pasującego do Twojego wyszukiwania ({{search}})." + "searchNoMatch": "Nie mogliśmy znaleźć retrospektywnego pasującego do Twojego wyszukiwania ({{search}}).", + "proNotSetupWarning": "Zasubskrybowałeś konto Pro Team, co oznacza, że możesz przyznać 19 innym kolegom konto Pro. Aby dodać je do subskrypcji, aby mogli zostać również Pro, przejdź na stronę swojego konta i dodaj ich e-maile (sekcja \"Twój Zespół\").", + "proNotSetupWarningAction": "Przejdź do strony konta" }, "PreviousGame": { "createdBy": "Utworzony przez", @@ -324,6 +326,7 @@ "header": "Twój plan", "plan": "Plan", "admins": "Administratorzy", + "members": "Członkowie", "youAreOwner": "Jesteś właścicielem tego planu poprzez poniższą subskrypcję.", "youAreMember": "Jesteś na tym planie poprzez subskrypcję kogoś innego.", "ownedBy": "Posiadane przez", @@ -510,4 +513,4 @@ ], "feedback": "Pozostaw trochę opinii" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/pt-BR.json b/frontend/src/translations/locales/pt-BR.json index 4ba03688..5a853a5d 100644 --- a/frontend/src/translations/locales/pt-BR.json +++ b/frontend/src/translations/locales/pt-BR.json @@ -27,7 +27,9 @@ "anonWarning": "Você está usando uma conta anônima. Talvez você queira fazer login para garantir que seus dados possam ser recuperados usando outro dispositivo.", "howDoesThatWork": "Como funciona isso?", "login": "Conectar-se", - "searchNoMatch": "Não foi possível encontrar um retrospectivo correspondente à sua pesquisa ({{search}})." + "searchNoMatch": "Não foi possível encontrar um retrospectivo correspondente à sua pesquisa ({{search}}).", + "proNotSetupWarning": "Você se inscreveu em uma conta de equipe Pro, o que significa que você pode conceder uma conta Pro a 19 outros colegas. Para adicioná-los à sua assinatura para que possam se tornar o Pro. por favor, acesse a página da sua Conta e adicione os e-mails deles (seção 'Sua Equipe').", + "proNotSetupWarningAction": "Ir para a página da Conta" }, "PreviousGame": { "createdBy": "Criado Por", @@ -324,6 +326,7 @@ "header": "Seu plano", "plan": "Planejamento", "admins": "Administradores", + "members": "membros", "youAreOwner": "Você é o proprietário deste plano, através da assinatura abaixo.", "youAreMember": "Você está nesse plano por meio da assinatura de outra pessoa.", "ownedBy": "Possuído por", @@ -510,4 +513,4 @@ ], "feedback": "Deixe alguns comentários" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/pt-PT.json b/frontend/src/translations/locales/pt-PT.json index 55889c04..d0a831c4 100644 --- a/frontend/src/translations/locales/pt-PT.json +++ b/frontend/src/translations/locales/pt-PT.json @@ -27,7 +27,9 @@ "anonWarning": "Você está usando uma conta anônima. Talvez você queira fazer login para garantir que seus dados possam ser recuperados usando outro dispositivo.", "howDoesThatWork": "Como funciona isso?", "login": "Conectar-se", - "searchNoMatch": "Não foi possível encontrar um retrospectivo correspondente à sua pesquisa ({{search}})." + "searchNoMatch": "Não foi possível encontrar um retrospectivo correspondente à sua pesquisa ({{search}}).", + "proNotSetupWarning": "Você se inscreveu em uma conta de equipe Pro, o que significa que você pode conceder uma conta Pro a 19 outros colegas. Para adicioná-los à sua assinatura para que possam se tornar o Pro. por favor, acesse a página da sua Conta e adicione os e-mails deles (seção 'Sua Equipe').", + "proNotSetupWarningAction": "Ir para a página da Conta" }, "PreviousGame": { "createdBy": "Criado Por", @@ -324,6 +326,7 @@ "header": "Seu plano", "plan": "Planejamento", "admins": "Administradores", + "members": "membros", "youAreOwner": "Você é o proprietário deste plano, através da assinatura abaixo.", "youAreMember": "Você está nesse plano por meio da assinatura de outra pessoa.", "ownedBy": "Possuído por", @@ -510,4 +513,4 @@ ], "feedback": "Deixe alguns comentários" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/uk-UA.json b/frontend/src/translations/locales/uk-UA.json index 3e27528d..648d9a14 100644 --- a/frontend/src/translations/locales/uk-UA.json +++ b/frontend/src/translations/locales/uk-UA.json @@ -27,7 +27,9 @@ "anonWarning": "Ви використовуєте анонімний обліковий запис. Ви можете увійти в систему, щоб переконатися, що ваші дані можуть бути отримані при використанні іншого пристрою.", "howDoesThatWork": "Як це працює?", "login": "Логін", - "searchNoMatch": "Ми не змогли знайти ретроспектив, що відповідає вашому запиту ({{search}})." + "searchNoMatch": "Ми не змогли знайти ретроспектив, що відповідає вашому запиту ({{search}}).", + "proNotSetupWarning": "Ви підписалися на обліковий запис професійної команди. Це означає, що можна надати 19 інших колег Pro аккаунту. Щоб додати їх у вашу підписку, щоб вони також могли стати Pro. будь ласка, перейдіть на сторінку свого облікового запису та додайте їхні електронні листи ('Розділ «Ваші команди»).", + "proNotSetupWarningAction": "Перейти на сторінку облікового запису" }, "PreviousGame": { "createdBy": "Створено", @@ -324,6 +326,7 @@ "header": "Ваш План", "plan": "План", "admins": "Адміністратори", + "members": "Члени", "youAreOwner": "Ви власник цього плану через нижченаведену підписку.", "youAreMember": "Ви використовуєте цей тарифний план з чужої підписки.", "ownedBy": "Власник", @@ -510,4 +513,4 @@ ], "feedback": "Залишити відгук" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/zh-CN.json b/frontend/src/translations/locales/zh-CN.json index b173cae4..cd19f8ad 100644 --- a/frontend/src/translations/locales/zh-CN.json +++ b/frontend/src/translations/locales/zh-CN.json @@ -27,7 +27,9 @@ "anonWarning": "您正在使用匿名帐户。您可能想要登录以确保您的数据可以在使用其他设备时检索。", "howDoesThatWork": "这是如何运作的?", "login": "登录", - "searchNoMatch": "我们找不到匹配您搜索的 ({{search}} )的追溯。" + "searchNoMatch": "我们找不到匹配您搜索的 ({{search}} )的追溯。", + "proNotSetupWarning": "您已经订阅了专业团队账户,这意味着您可以授予其他19位同事专业版账户。 将他们添加到您的订阅,这样他们也可以成为专业版。 请转到您的帐户页面并添加他们的电子邮件('您的团队'部分)。", + "proNotSetupWarningAction": "转到帐户页面" }, "PreviousGame": { "createdBy": "创建者", @@ -324,6 +326,7 @@ "header": "您的计划", "plan": "计划", "admins": "管理员", + "members": "成员", "youAreOwner": "您是此计划的所有者,通过下面的订阅。", "youAreMember": "您正在通过其他人的订阅加入此计划。", "ownedBy": "拥有者", @@ -510,4 +513,4 @@ ], "feedback": "留下一些反馈" } -} \ No newline at end of file +} diff --git a/frontend/src/translations/locales/zh-TW.json b/frontend/src/translations/locales/zh-TW.json index 23388f0b..2d1b1335 100644 --- a/frontend/src/translations/locales/zh-TW.json +++ b/frontend/src/translations/locales/zh-TW.json @@ -27,7 +27,9 @@ "anonWarning": "", "howDoesThatWork": "這是如何運作的?", "login": "", - "searchNoMatch": "" + "searchNoMatch": "", + "proNotSetupWarning": "", + "proNotSetupWarningAction": "" }, "PreviousGame": { "createdBy": "由...製作", @@ -324,6 +326,7 @@ "header": "你的計劃", "plan": "計劃", "admins": "", + "members": "", "youAreOwner": "通過以下訂閱,您是該計劃的所有者。", "youAreMember": "您通過其他人的訂閱參與此計劃。", "ownedBy": "", @@ -510,4 +513,4 @@ ], "feedback": "" } -} \ No newline at end of file +} diff --git a/frontend/src/views/Home.tsx b/frontend/src/views/Home.tsx index 0c4eb05e..be016d9d 100644 --- a/frontend/src/views/Home.tsx +++ b/frontend/src/views/Home.tsx @@ -26,6 +26,7 @@ import ClosableAlert from 'components/ClosableAlert'; import SplitButton from 'components/SplitButton/SplitButton'; import SearchBar from './game/SearchBar'; import { NameEditor } from 'molecules/NameEditor'; +import { SetupProPrompt } from './home/SetupProPrompt'; function Home() { const navigate = useNavigate(); @@ -88,6 +89,7 @@ function Home() { return ( <> + {user && user.accountType === 'anonymous' ? ( {t('Home.anonWarning')}    diff --git a/frontend/src/views/account/AccountPage.tsx b/frontend/src/views/account/AccountPage.tsx index 9bd61d0f..ff91614a 100644 --- a/frontend/src/views/account/AccountPage.tsx +++ b/frontend/src/views/account/AccountPage.tsx @@ -47,15 +47,8 @@ function AccountPage() { updateAdmins(admins); }, []); - const ownsThePlan = - user && - !!user.ownSubscriptionsId && - user.ownSubscriptionsId === user.subscriptionsId; - - const onSomebodysPlan = - user && - !!user.subscriptionsId && - user.ownSubscriptionsId !== user.subscriptionsId; + const ownsThePlan = user && !!user.ownSubscriptionsId; + const onSomebodysPlan = user && !!user.plan && !user.ownSubscriptionsId; const isPlanAdmin = user && @@ -135,6 +128,19 @@ function AccountPage() { {user.plan} + {onSomebodysPlan ? ( + + {t('AccountPage.plan.members')} + + {[user.planOwnerEmail, ...(user.planMembers || [])] + .filter(Boolean) + .map((email, i) => ( + + ))} + + + ) : null} + {t('AccountPage.plan.admins')} diff --git a/frontend/src/views/home/SetupProPrompt.tsx b/frontend/src/views/home/SetupProPrompt.tsx new file mode 100644 index 00000000..86119435 --- /dev/null +++ b/frontend/src/views/home/SetupProPrompt.tsx @@ -0,0 +1,36 @@ +import ClosableAlert from 'components/ClosableAlert'; +import useUser from 'state/user/useUser'; +import { useTranslation } from 'react-i18next'; +import { Button } from '@mui/material'; +import { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; + +export function SetupProPrompt() { + const user = useUser(); + const { t } = useTranslation(); + const navigate = useNavigate(); + + const handleAction = useCallback(() => { + navigate('/account'); + }, [navigate]); + + const shouldDisplay = + user && + user.pro && + user.plan === 'team' && + user.ownPlan && + user.planMembers?.length === 0; + + if (!shouldDisplay) { + return null; + } + + return ( + + {t('Home.proNotSetupWarning')}  + + + ); +} diff --git a/frontend/src/views/subscribe/SubscribePage.tsx b/frontend/src/views/subscribe/SubscribePage.tsx index 24a10f74..3f1db2c8 100644 --- a/frontend/src/views/subscribe/SubscribePage.tsx +++ b/frontend/src/views/subscribe/SubscribePage.tsx @@ -210,10 +210,10 @@ function SubscriberPage() { return (
Retrospected Pro
- {user && user.pro && !user.subscriptionsId ? ( + {user && user.pro ? ( {t('SubscribePage.alertAlreadyPro')} ) : null} - {user && user.subscriptionsId && !user.trial ? ( + {user && !!user.plan && !user.trial ? ( {t('SubscribePage.alertAlreadySubscribed')}