From 2c7edc5319d6d0545f0820b0ca7439225b2bc91c Mon Sep 17 00:00:00 2001 From: Gokhan Sari Date: Mon, 6 Mar 2023 17:51:48 +0300 Subject: [PATCH] Improve e-mail validation and subscription checks on frontend (#161) * Fix dropdown article links on docs * Lil fixes here and there * Update index.tsx * Fix playwright tests --- .../user_access_token_form_auth.go | 118 +++++++++--------- .../middleware/permission/organization.go | 13 +- .../permission/organization_plan.go | 21 ---- .../{user_permission.go => user.go} | 6 - backend/pkg/restapi/site/main.go | 2 +- backend/pkg/restapi/sitereport/main.go | 52 ++++---- backend/pkg/restapi/user/main.go | 6 +- backend/pkg/service/site/read_public.go | 6 +- frontend/components/AuthHandler/index.tsx | 2 +- .../components/DocsArticle/Menu/index.tsx | 8 +- .../EmailVerificationHandler/index.tsx | 48 +++++++ .../components/NoSubscriptionBlock/index.tsx | 31 +++++ frontend/components/Pricing/Faq/index.tsx | 2 +- .../components/SubscriptionHandler/index.tsx | 54 ++++++++ .../UnverifiedEmailAddressBlock/index.tsx | 19 --- frontend/components/index.ts | 4 +- frontend/components/withAuth/index.tsx | 46 +++---- frontend/pages/s.tsx | 10 +- frontend/tests/common/signUp.ts | 2 +- 19 files changed, 271 insertions(+), 179 deletions(-) delete mode 100644 backend/pkg/restapi/middleware/permission/organization_plan.go rename backend/pkg/restapi/middleware/permission/{user_permission.go => user.go} (94%) create mode 100644 frontend/components/EmailVerificationHandler/index.tsx create mode 100644 frontend/components/NoSubscriptionBlock/index.tsx create mode 100644 frontend/components/SubscriptionHandler/index.tsx delete mode 100644 frontend/components/UnverifiedEmailAddressBlock/index.tsx diff --git a/backend/pkg/restapi/middleware/authentication/user_access_token_form_auth.go b/backend/pkg/restapi/middleware/authentication/user_access_token_form_auth.go index 5d9686ba..322ef063 100644 --- a/backend/pkg/restapi/middleware/authentication/user_access_token_form_auth.go +++ b/backend/pkg/restapi/middleware/authentication/user_access_token_form_auth.go @@ -16,77 +16,75 @@ import ( "gorm.io/gorm" ) -func NewUserAccessTokenFormAuth() fiber.Handler { - return func(c *fiber.Ctx) error { - dateTime := time.Now() +func NewUserAccessTokenFormAuth(c *fiber.Ctx) error { + dateTime := time.Now() - dp := dm.Get(c) + dp := dm.Get(c) - userAccessToken := c.FormValue("user-access-token") + userAccessToken := c.FormValue("user-access-token") - if userAccessToken == "" { - return c.Next() - } - - modelUserAccessToken := &model.UserAccessToken{} + if userAccessToken == "" { + return c.Next() + } - err := dp.Postgres(). - Model(&model.UserAccessToken{}). - Where("token = ?", userAccessToken). - First(modelUserAccessToken). - Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.ErrUnauthorized - } + modelUserAccessToken := &model.UserAccessToken{} - return err + err := dp.Postgres(). + Model(&model.UserAccessToken{}). + Where("token = ?", userAccessToken). + First(modelUserAccessToken). + Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return fiber.ErrUnauthorized } - modelUser := &model.User{} + return err + } - err = dp.Postgres(). - Model(&model.User{}). - Where("is_active is true"). - Where("id = ?", modelUserAccessToken.UserId). - First(modelUser). - Error - if err != nil { - return err - } + modelUser := &model.User{} - modelOrganization := &model.Organization{} + err = dp.Postgres(). + Model(&model.User{}). + Where("is_active is true"). + Where("id = ?", modelUserAccessToken.UserId). + First(modelUser). + Error + if err != nil { + return err + } - err = dp.Postgres(). - Model(&model.Organization{}). - Joins("Plan"). - Where("organizations.id = ?", modelUser.OrganizationId). - First(modelOrganization). - Error - if err != nil { - return err - } + modelOrganization := &model.Organization{} - c.Locals("auth", &Auth{ - Kind: pointer.Get(AuthKindRestApiUserAccessToken), - Organization: modelOrganization, - User: modelUser, - UserAccessToken: modelUserAccessToken, - }) - - fibersentry.GetHubFromContext(c).Scope().SetUser(sentry.User{ - Email: modelUser.Email, - ID: strconv.Itoa(int(modelUser.Id)), - }) - - err = dp.Redis(). - Set(depot.Ctx, modelUserAccessToken.LastUsedAtRedisKey(), dateTime.Unix(), time.Hour). - Err() - if err != nil { - // TODO: handle the error better - log.Println(err) - } + err = dp.Postgres(). + Model(&model.Organization{}). + Joins("Plan"). + Where("organizations.id = ?", modelUser.OrganizationId). + First(modelOrganization). + Error + if err != nil { + return err + } - return c.Next() + c.Locals("auth", &Auth{ + Kind: pointer.Get(AuthKindRestApiUserAccessToken), + Organization: modelOrganization, + User: modelUser, + UserAccessToken: modelUserAccessToken, + }) + + fibersentry.GetHubFromContext(c).Scope().SetUser(sentry.User{ + Email: modelUser.Email, + ID: strconv.Itoa(int(modelUser.Id)), + }) + + err = dp.Redis(). + Set(depot.Ctx, modelUserAccessToken.LastUsedAtRedisKey(), dateTime.Unix(), time.Hour). + Err() + if err != nil { + // TODO: handle the error better + log.Println(err) } + + return c.Next() } diff --git a/backend/pkg/restapi/middleware/permission/organization.go b/backend/pkg/restapi/middleware/permission/organization.go index 0c8b9e38..e15290a1 100644 --- a/backend/pkg/restapi/middleware/permission/organization.go +++ b/backend/pkg/restapi/middleware/permission/organization.go @@ -2,14 +2,17 @@ package permission import ( "github.com/gofiber/fiber/v2" - am "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/authentication" + "github.com/th0th/poeticmetric/backend/pkg/restapi/helpers" + "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/authentication" ) -func OrganizationNonStripeCustomer(c *fiber.Ctx) error { - auth := am.Get(c) +func OrganizationSubscription(c *fiber.Ctx) error { + auth := c.Locals("auth").(*authentication.Auth) - if auth.Organization.StripeCustomerId != nil { - return fiber.ErrForbidden + if auth.Organization.Plan == nil { + return c. + Status(fiber.StatusForbidden). + JSON(helpers.Detail("You need to subscribe to continue to use PoeticMetric")) } return c.Next() diff --git a/backend/pkg/restapi/middleware/permission/organization_plan.go b/backend/pkg/restapi/middleware/permission/organization_plan.go deleted file mode 100644 index 83776b28..00000000 --- a/backend/pkg/restapi/middleware/permission/organization_plan.go +++ /dev/null @@ -1,21 +0,0 @@ -package permission - -import ( - "reflect" - - "github.com/gofiber/fiber/v2" - "github.com/th0th/poeticmetric/backend/pkg/restapi/helpers" - "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/authentication" -) - -func OrganizationPlanFeatureFlag(flag string) fiber.Handler { - return func(c *fiber.Ctx) error { - auth := authentication.Get(c) - - if !reflect.ValueOf(auth.Organization.Plan).FieldByName(flag).Bool() { - return c.Status(fiber.StatusForbidden).JSON(helpers.Detail("You need to upgrade your subscription.")) - } - - return c.Next() - } -} diff --git a/backend/pkg/restapi/middleware/permission/user_permission.go b/backend/pkg/restapi/middleware/permission/user.go similarity index 94% rename from backend/pkg/restapi/middleware/permission/user_permission.go rename to backend/pkg/restapi/middleware/permission/user.go index 84bd68b0..7667ea7b 100644 --- a/backend/pkg/restapi/middleware/permission/user_permission.go +++ b/backend/pkg/restapi/middleware/permission/user.go @@ -78,12 +78,6 @@ func UserBasicAuthenticated(c *fiber.Ctx) error { })(c) } -func UserEmailVerified(c *fiber.Ctx) error { - return User(&userPermissionMiddlewareConfig{ - IsEmailVerified: pointer.Get(true), - })(c) -} - func UserOwner(c *fiber.Ctx) error { return User(&userPermissionMiddlewareConfig{ IsOrganizationOwner: pointer.Get(true), diff --git a/backend/pkg/restapi/site/main.go b/backend/pkg/restapi/site/main.go index 0ec7457d..89ea6f3c 100644 --- a/backend/pkg/restapi/site/main.go +++ b/backend/pkg/restapi/site/main.go @@ -10,7 +10,7 @@ func Add(app *fiber.App) { group.Get("/public/:domain", readPublic) - group.Use(permission.UserAuthenticated) + group.Use(permission.UserAuthenticated, permission.OrganizationSubscription) group.Get("/", list) group.Post("/", create) diff --git a/backend/pkg/restapi/sitereport/main.go b/backend/pkg/restapi/sitereport/main.go index 07d038f8..0bca94f4 100644 --- a/backend/pkg/restapi/sitereport/main.go +++ b/backend/pkg/restapi/sitereport/main.go @@ -2,30 +2,28 @@ package sitereport import ( "encoding/json" - "errors" "fmt" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/skip" "github.com/th0th/poeticmetric/backend/pkg/model" "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/authentication" dm "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/depot" "github.com/th0th/poeticmetric/backend/pkg/restapi/middleware/permission" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/browsername" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/browserversion" + "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/filter" + language2 "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/language" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/operatingsystemname" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/operatingsystemversion" + path2 "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/path" + "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/referrerpath" + "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/referrersite" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/utmcampaign" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/utmcontent" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/utmmedium" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/utmsource" "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/utmterm" - "gorm.io/gorm" - - "github.com/gofiber/fiber/v2" - "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/filter" - language2 "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/language" - path2 "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/path" - "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/referrerpath" - "github.com/th0th/poeticmetric/backend/pkg/service/sitereport/referrersite" ) const localsFiltersKey = "filters" @@ -34,10 +32,19 @@ const localsPaginationCursorKey = "paginationCursor" func Add(app *fiber.App) { baseGroup := app.Group("/site-reports", filtersMiddleware) - baseGroup.Post("/export/reports", authentication.NewUserAccessTokenFormAuth(), authenticatedOrPublicMiddleware, exportReports) - baseGroup.Post("/export/events", authentication.NewUserAccessTokenFormAuth(), authenticatedOrPublicMiddleware, exportEvents) + exportGroup := baseGroup.Use( + authentication.NewUserAccessTokenFormAuth, + skip.New(permission.UserAuthenticated, isPublic), + skip.New(permission.OrganizationSubscription, isPublic), + ) + + exportGroup.Post("/export/reports", exportReports) + exportGroup.Post("/export/events", exportEvents) - group := baseGroup.Use(authenticatedOrPublicMiddleware) + group := baseGroup.Use( + skip.New(permission.UserAuthenticated, isPublic), + skip.New(permission.OrganizationSubscription, isPublic), + ) group.Get("/browser-name", paginationCursorMiddleware[browsername.PaginationCursor], browserName) group.Get("/browser-version", paginationCursorMiddleware[browserversion.PaginationCursor], browserVersion) @@ -62,30 +69,21 @@ func Add(app *fiber.App) { group.Get("/visitor", visitor) } -func authenticatedOrPublicMiddleware(c *fiber.Ctx) error { +func isPublic(c *fiber.Ctx) bool { dp := dm.Get(c) modelSite := &model.Site{} err := dp.Postgres(). Model(&model.Site{}). - Select("is_public"). - Where("id = ?", getFilters(c).SiteId). + Joins("inner join organizations on organizations.id = sites.organization_id"). + Select("sites.is_public"). + Where("organizations.plan_id is not null"). + Where("sites.id = ?", getFilters(c).SiteId). First(modelSite). Error - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return fiber.ErrNotFound - } - - return err - } - - if modelSite.IsPublic { - return c.Next() - } - return permission.UserAuthenticated(c) + return err == nil && modelSite.IsPublic } func getFilters(c *fiber.Ctx) *filter.Filters { diff --git a/backend/pkg/restapi/user/main.go b/backend/pkg/restapi/user/main.go index 5951df5e..d56a0f0f 100644 --- a/backend/pkg/restapi/user/main.go +++ b/backend/pkg/restapi/user/main.go @@ -11,7 +11,7 @@ func Add(app *fiber.App) { group.Get("/", permission.UserAuthenticated, list) group.Get("/me", permission.UserAuthenticated, readSelf) group.Patch("/me", permission.UserAuthenticated, updateSelf) - group.Post("/", permission.UserOwner, invite) + group.Post("/", permission.UserOwner, permission.OrganizationSubscription, invite) group.Post("/activate", permission.UserUnauthenticated, activate) group.Post("/change-password", permission.UserBasicAuthenticated, changePassword) group.Post("/make-owner", permission.UserOwner, makeOwner) @@ -21,6 +21,6 @@ func Add(app *fiber.App) { group.Post("/verify-email-address", verifyEmailAddress) group.Get("/:id", permission.UserAuthenticated, read) - group.Delete("/:id", permission.UserOwner, destroy) - group.Patch("/:id", permission.UserOwner, update) + group.Delete("/:id", permission.UserOwner, permission.OrganizationSubscription, destroy) + group.Patch("/:id", permission.UserOwner, permission.OrganizationSubscription, update) } diff --git a/backend/pkg/service/site/read_public.go b/backend/pkg/service/site/read_public.go index 09b396d8..d0a5173e 100644 --- a/backend/pkg/service/site/read_public.go +++ b/backend/pkg/service/site/read_public.go @@ -10,8 +10,10 @@ func ReadPublic(dp *depot.Depot, domain string) (*Site, error) { err := dp.Postgres(). Model(&model.Site{}). - Where("is_public is true"). - Where("domain = ?", domain). + Joins("inner join organizations on organizations.id = sites.organization_id"). + Where("organizations.plan_id is not null"). + Where("sites.is_public is true"). + Where("sites.domain = ?", domain). First(site). Error if err != nil { diff --git a/frontend/components/AuthHandler/index.tsx b/frontend/components/AuthHandler/index.tsx index bb00e9ae..1836fb32 100644 --- a/frontend/components/AuthHandler/index.tsx +++ b/frontend/components/AuthHandler/index.tsx @@ -9,7 +9,7 @@ type AuthHandlerProps = { const fetcher = getFetcher(true, false); -export function AuthHandler({ children }: AuthHandlerProps) { +export function AuthHandler({ children }: AuthHandlerProps) { const { data: userData, isValidating: isValidatingUser, mutate: mutateUserData } = useSWR("/users/me", fetcher); const { data: organizationData, diff --git a/frontend/components/DocsArticle/Menu/index.tsx b/frontend/components/DocsArticle/Menu/index.tsx index afb96877..b42a9ae7 100644 --- a/frontend/components/DocsArticle/Menu/index.tsx +++ b/frontend/components/DocsArticle/Menu/index.tsx @@ -31,7 +31,13 @@ export function Menu({ article: articleFromProps, categories, className, ...prop
{category.title}
{category.articles.map((article) => ( - {article.title} + + {article.title} + ))} ))} diff --git a/frontend/components/EmailVerificationHandler/index.tsx b/frontend/components/EmailVerificationHandler/index.tsx new file mode 100644 index 00000000..97b7154f --- /dev/null +++ b/frontend/components/EmailVerificationHandler/index.tsx @@ -0,0 +1,48 @@ +import { useRouter } from "next/router"; +import React, { useContext, useEffect, useMemo } from "react"; +import { Container } from "react-bootstrap"; +import { AuthContext } from "../../contexts"; +import { Layout } from "../Layout"; +import { Title } from "../Title"; + +export type EmailVerificationHandlerProps = { + children: React.ReactNode; +}; + +const routerQueryBlock = "unverified-email"; + +export function EmailVerificationHandler({ children }: EmailVerificationHandlerProps) { + const router = useRouter(); + const { user } = useContext(AuthContext); + + const shouldBlock = useMemo( + () => user !== null + && !user.isEmailVerified + && !router.asPath.startsWith("/settings"), + [router.asPath, user], + ); + + useEffect(() => { + if (shouldBlock && router.query.block !== routerQueryBlock) { + router.replace({ pathname: router.pathname, query: { ...router.query, block: routerQueryBlock } }, undefined, { scroll: false }); + } + }, [router, shouldBlock]); + + return shouldBlock ? ( + + Please verify your e-mail address to continue + + +
+ + +

Please verify your e-mail address

+ +
You need to verify your e-mail address to continue.
+
+
+
+ ) : ( + <>{children} + ); +} diff --git a/frontend/components/NoSubscriptionBlock/index.tsx b/frontend/components/NoSubscriptionBlock/index.tsx new file mode 100644 index 00000000..ce9ca915 --- /dev/null +++ b/frontend/components/NoSubscriptionBlock/index.tsx @@ -0,0 +1,31 @@ +import Link from "next/link"; +import React, { useContext } from "react"; +import { Container, Spinner } from "react-bootstrap"; +import { AuthContext } from "../../contexts"; +import { Layout } from "../Layout"; + +export function NoSubscriptionBlock() { + const { organization } = useContext(AuthContext); + + return ( + + + {organization === null || organization.plan !== null ? ( + + ) : ( +
+ + +

+ +
You need to verify your e-mail address to continue.
+ +
+ Go to billing +
+
+ )} +
+
+ ); +} diff --git a/frontend/components/Pricing/Faq/index.tsx b/frontend/components/Pricing/Faq/index.tsx index 8248d72f..1f304438 100644 --- a/frontend/components/Pricing/Faq/index.tsx +++ b/frontend/components/Pricing/Faq/index.tsx @@ -52,7 +52,7 @@ export function Faq({ className, ...props }: FaqProps) { - Is there something else you want to know? We are always available at + {"Is there something else you want to know? We are always available at "} support@poeticmetric.com . diff --git a/frontend/components/SubscriptionHandler/index.tsx b/frontend/components/SubscriptionHandler/index.tsx new file mode 100644 index 00000000..adda4f2d --- /dev/null +++ b/frontend/components/SubscriptionHandler/index.tsx @@ -0,0 +1,54 @@ +import Link from "next/link"; +import { useRouter } from "next/router"; +import React, { useContext, useEffect, useMemo } from "react"; +import { Container } from "react-bootstrap"; +import { AuthContext } from "../../contexts"; +import { Layout } from "../Layout"; +import { Title } from "../Title"; + +export type SubscriptionHandlerProps = { + children: React.ReactNode; +}; + +const routerQueryBlock = "no-subscription"; + +export function SubscriptionHandler({ children }: SubscriptionHandlerProps) { + const router = useRouter(); + const { organization } = useContext(AuthContext); + + const shouldBlock = useMemo( + () => organization !== null + && organization.plan === null + && !router.asPath.startsWith("/billing") + && !router.asPath.startsWith("/settings"), + [organization, router.asPath], + ); + + useEffect(() => { + if (shouldBlock && router.query.block !== routerQueryBlock) { + router.replace({ pathname: router.pathname, query: { ...router.query, block: routerQueryBlock } }, undefined, { scroll: false }); + } + }, [router, shouldBlock]); + + return shouldBlock ? ( + + Subscription required + + +
+ + +

You don't have an active subscription

+ +
Please subscribe to continue to use PoeticMetric.
+ +
+ Go to billing +
+
+
+
+ ) : ( + <>{children} + ); +} diff --git a/frontend/components/UnverifiedEmailAddressBlock/index.tsx b/frontend/components/UnverifiedEmailAddressBlock/index.tsx deleted file mode 100644 index 48c50186..00000000 --- a/frontend/components/UnverifiedEmailAddressBlock/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from "react"; -import { Container } from "react-bootstrap"; -import { Layout } from "../Layout"; - -export function UnverifiedEmailAddressBlock() { - return ( - - -
- - -

Please verify your e-mail address

- -
You need to verify your e-mail address to continue.
-
-
-
- ); -} diff --git a/frontend/components/index.ts b/frontend/components/index.ts index 83a8d5cf..f3350c11 100644 --- a/frontend/components/index.ts +++ b/frontend/components/index.ts @@ -12,6 +12,7 @@ export * from "./ChartTooltip"; export * from "./Description"; export * from "./DocsArticle"; export * from "./EmailAddressVerification"; +export * from "./EmailVerificationHandler"; export * from "./Empty"; export * from "./FavIcon"; export * from "./Features"; @@ -22,6 +23,7 @@ export * from "./Manifesto"; export * from "./Markdown"; export * from "./MetaOpenGraph"; export * from "./MockWindow"; +export * from "./NoSubscriptionBlock"; export * from "./NotFound"; export * from "./PasswordRecovery"; export * from "./PasswordReset"; @@ -38,6 +40,7 @@ export * from "./SignUp"; export * from "./SiteForm"; export * from "./SiteReports"; export * from "./Sites"; +export * from "./SubscriptionHandler"; export * from "./SwrConfig"; export * from "./SyntaxHighlighter"; export * from "./Team"; @@ -45,7 +48,6 @@ export * from "./TeamMemberForm"; export * from "./TermsOfService"; export * from "./Title"; export * from "./ToastsHandler"; -export * from "./UnverifiedEmailAddressBlock"; export * from "./UserMenu"; export * from "./withAuth"; export * from "./withParentSize"; diff --git a/frontend/components/withAuth/index.tsx b/frontend/components/withAuth/index.tsx index d3382200..513c0a98 100644 --- a/frontend/components/withAuth/index.tsx +++ b/frontend/components/withAuth/index.tsx @@ -1,14 +1,15 @@ import { NextPage } from "next"; import { AppProps } from "next/app"; import { useRouter } from "next/router"; -import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; +import React, { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { Spinner } from "react-bootstrap"; +import { EmailVerificationHandler, SubscriptionHandler } from ".."; import { AuthContext, ToastsContext } from "../../contexts"; import { api } from "../../helpers"; -import { UnverifiedEmailAddressBlock } from "../UnverifiedEmailAddressBlock"; type State = { isBootstrapStatusChecked: boolean; + isReady: boolean; }; type WrappedProps = { @@ -18,10 +19,9 @@ type WrappedProps = { export function withAuth(Page: NextPage, authenticated: boolean, ownerOnly?: true) { function Wrapped({ pageProps }: WrappedProps) { const router = useRouter(); - const { isReady, user } = useContext(AuthContext); + const { isReady: isAuthReady, user } = useContext(AuthContext); const { addToast } = useContext(ToastsContext); - const isBootstrapStatusChecked = useRef(process.env.NEXT_PUBLIC_HOSTED === "true"); - const [state, setState] = useState({ isBootstrapStatusChecked: process.env.NEXT_PUBLIC_HOSTED === "true" }); + const [state, setState] = useState({ isBootstrapStatusChecked: process.env.NEXT_PUBLIC_HOSTED === "true", isReady: false }); const isPermitted = useMemo(() => { if (!state.isBootstrapStatusChecked) { @@ -59,41 +59,33 @@ export function withAuth(Page: NextPage, authenticated: boolean, ownerOnly?: tru }, [addToast, router]); useEffect(() => { - if (!isBootstrapStatusChecked.current) { - isBootstrapStatusChecked.current = true; - + if (!state.isBootstrapStatusChecked) { checkBootstrapStatus(); } - }, [checkBootstrapStatus]); + }, [checkBootstrapStatus, state.isBootstrapStatusChecked]); useEffect(() => { - if (!state.isBootstrapStatusChecked) { + if (!state.isBootstrapStatusChecked || !isAuthReady) { return; } - if (!isPermitted && isReady) { + if (!isPermitted) { if (authenticated) { router.replace(`/sign-in?next=${router.asPath}`); } else { router.replace(typeof router.query.next === "string" ? router.query.next : "/sites"); } } - }, [isPermitted, isReady, router, state.isBootstrapStatusChecked, user]); - - if (!isReady || !isPermitted) { - return ( - - ); - } - - if (user !== null && !user.isEmailVerified) { - return ( - - ); - } - - return ( - + }, [isAuthReady, isPermitted, router, state.isBootstrapStatusChecked]); + + return !isAuthReady || !isPermitted ? ( + + ) : ( + + + + + ); } diff --git a/frontend/pages/s.tsx b/frontend/pages/s.tsx index 39e80b2c..33a3ed31 100644 --- a/frontend/pages/s.tsx +++ b/frontend/pages/s.tsx @@ -1,7 +1,7 @@ import { useRouter } from "next/router"; -import { useContext, useEffect, useMemo } from "react"; +import React, { useContext, useEffect, useMemo } from "react"; import { Container, Spinner } from "react-bootstrap"; -import { Layout, SiteReports } from "../components"; +import { Layout, SiteReports, Title } from "../components"; import { ToastsContext } from "../contexts"; import { usePublicSite } from "../hooks"; @@ -33,7 +33,11 @@ export default function PublicSiteReports() { {site === undefined ? ( ) : ( - + <> + {`Reports for ${site.name}`} + + + )} diff --git a/frontend/tests/common/signUp.ts b/frontend/tests/common/signUp.ts index ac61e6d2..f0c9e7f5 100644 --- a/frontend/tests/common/signUp.ts +++ b/frontend/tests/common/signUp.ts @@ -13,7 +13,7 @@ export async function signUp(test: typeof baseTest, context: BrowserContext, pag await page.getByRole("button", { name: "Sign up" }).click(); await expect(page.getByText("Welcome to PoeticMetric!")).toBeVisible(); - await page.waitForURL("/sites"); + await page.waitForURL("/sites?block=unverified-email"); await expect(page.getByRole("heading", { name: "Please verify your e-mail address" })).toBeVisible(); });