diff --git a/.env.development b/.env.development index 63cf2ffdd..ccfc26f1a 100644 --- a/.env.development +++ b/.env.development @@ -1,9 +1,9 @@ REACT_APP_GTM_ID= -REACT_APP_API_URL=https://dv-crowd.app-quality.com/api +REACT_APP_API_URL=https://dev.tryber.me/api REACT_APP_DEFAULT_TOKEN= -REACT_APP_CROWD_WP_URL=https://dv-crowd.app-quality.com +REACT_APP_CROWD_WP_URL=https://dev.tryber.me REACT_APP_DATADOG_CLIENT_TOKEN= diff --git a/package-lock.json b/package-lock.json index 23c4ce3fd..dafcf1122 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7455,9 +7455,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001327", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", - "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", + "version": "1.0.30001487", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", + "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==", "devOptional": true, "funding": [ { @@ -7467,6 +7467,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ] }, @@ -35094,9 +35098,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001327", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", - "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", + "version": "1.0.30001487", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz", + "integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==", "devOptional": true }, "capture-exit": { diff --git a/public/index.html b/public/index.html index f6b2a4461..5830670ff 100644 --- a/public/index.html +++ b/public/index.html @@ -75,7 +75,7 @@ diff --git a/src/Page.tsx b/src/Page.tsx index 217bed99c..68f00edfe 100644 --- a/src/Page.tsx +++ b/src/Page.tsx @@ -26,6 +26,7 @@ import referralStore from "./redux/referral"; import { refreshUser } from "./redux/user/actions/refreshUser"; import BugForm from "./pages/BugForm"; import ThankYouPage from "./pages/ThankYou"; +import VdpPage from "./pages/VDP"; if (process.env.REACT_APP_DATADOG_CLIENT_TOKEN) { datadogLogs.init({ @@ -101,6 +102,7 @@ function Page() { + { const { user, error, isLoading } = useUser(); const history = useHistory(); + const isPublicUser = useAppSelector( + (state) => state.publicUserPages.isPublic + ); if (isLoading) { return ; @@ -32,7 +36,7 @@ const NotLoggedOnly = ({ return ( <> - + {!isPublicUser && } {children} ); diff --git a/src/features/PageTemplate.tsx b/src/features/PageTemplate.tsx index 7f437a4ae..41098dc42 100644 --- a/src/features/PageTemplate.tsx +++ b/src/features/PageTemplate.tsx @@ -1,10 +1,13 @@ import { Container, PageTitle } from "@appquality/appquality-design-system"; -import React, { FC } from "react"; +import React, { FC, useEffect } from "react"; import GoogleTagManager from "./GoogleTagManager"; import LoggedOnly from "./LoggedOnly"; import NotLoggedOnly from "./NotLoggedOnly"; import TesterSidebar from "./TesterSidebar"; +import { useParams } from "react-router-dom"; +import { resetUserToken, setUserTokenPublic } from "src/redux/publicUserPages"; +import { useAppDispatch } from "src/store"; const ContentTemplate = ({ title, @@ -55,6 +58,8 @@ export const PageTemplate: FC<{ containerClass = "aq-pb-3", route, }) => { + const dispatch = useAppDispatch(); + const LoggedStatusWrapper = shouldBeLoggedIn ? ({ children }: { children: React.ReactNode }) => ( {children} @@ -63,6 +68,16 @@ export const PageTemplate: FC<{ {children} ); + /** + * In the /VDP page we set the user token to public in order to allow the user to submit a bug report. + * Every other page should be private. + */ + const { token } = useParams<{ token?: string }>(); + useEffect(() => { + if (token) dispatch(setUserTokenPublic(token)); + else dispatch(resetUserToken()); + }, [token]); + // map children and separate Modal components from the rest const [modalChildren, pageChildren] = React.Children.toArray(children).reduce( (acc: React.ReactNode[], child) => { diff --git a/src/pages/BugForm/BugDetails/BugDetails.tsx b/src/pages/BugForm/BugDetails/BugDetails.tsx index 2a9ef5f9d..90fae8827 100644 --- a/src/pages/BugForm/BugDetails/BugDetails.tsx +++ b/src/pages/BugForm/BugDetails/BugDetails.tsx @@ -20,6 +20,7 @@ import { UseCase } from "src/pages/BugForm/BugDetails/select/UseCase"; import { TextareaField } from "src/pages/BugForm/BugDetails/TextareaField/TextareaField"; import { LabelWithHelper } from "src/pages/BugForm/LabelWithHelper/LabelWithHelper"; import useCampaignData from "src/pages/BugForm/useCampaignData"; +import { useAppSelector } from "src/store"; interface BugDetailsProps { className?: string; @@ -32,6 +33,10 @@ export const BugDetails = ({ className }: BugDetailsProps) => { `only screen and (min-width: ${aqBootstrapTheme.grid.breakpoints.lg})` ).matches; + const isPublicPage = useAppSelector( + (state) => state.publicUserPages.isPublic + ); + return ( { defaultValue: "[Phase / Section] - Briefly Issue description", })} /> -
- -
+ {!isPublicPage && ( +
+ +
+ )}
@@ -68,9 +75,11 @@ export const BugDetails = ({ className }: BugDetailsProps) => {
-
- -
+ {!isPublicPage && ( +
+ +
+ )} { const { mediaList } = useAppSelector((state) => state.bugForm); const [submitForm] = usePostUsersMeCampaignsByCampaignIdBugsMutation(); + const isPublicPage = useAppSelector( + (state) => state.publicUserPages.isPublic + ); + if (!data) return null; const initialBugValues: BugFormValues = { @@ -72,6 +76,14 @@ export const BugFormContainer = () => { initialBugValues.device = device[0].id.toString(); } + if (isPublicPage) { + /** + * In a public page, we don't want to show the usecase selector, + * so we set it to the default value (not a specific usecase) + */ + initialBugValues.useCase = "-1"; + } + data.additionalFields?.forEach( (f) => (initialBugValues.additional[f.slug] = "") ); @@ -191,32 +203,35 @@ export const BugFormContainer = () => { "The bug you reported has been uploaded successfully!", })} -
- (Link to bugs page):::BUGFORM_CONFIRMUPLOAD_TXT" - } - components={{ - bugs_link: ( - - ), - }} - defaults="Go to the Bugs page to check all the bugs you have uploaded." - /> -
+ {!isPublicPage && ( + + )} , "success", false ) ); + localStorage.setItem(data.id.toString(), JSON.stringify(additional)); now = new Date(); setDisableSubmit(false); diff --git a/src/pages/BugForm/FileUploader/FileList.tsx b/src/pages/BugForm/FileUploader/FileList.tsx index 27c156ff0..77b8f682f 100644 --- a/src/pages/BugForm/FileUploader/FileList.tsx +++ b/src/pages/BugForm/FileUploader/FileList.tsx @@ -41,6 +41,14 @@ export const FileList = () => { return typeof meta.error === "string" && meta.touched; }; const [deleteMedia] = useDeleteMediaMutation(); + + /** + * Hide delete action if the form is public + */ + const hideDeleteAction = useAppSelector( + (state) => state.publicUserPages.isPublic + ); + const onDelete = async (fileElement: FileElement) => { if (fileElement.status === "uploading") return; if ( @@ -95,7 +103,11 @@ export const FileList = () => { key={f.id} className="file-list-card" fileElement={f} - onDelete={f.status !== "uploading" ? () => onDelete(f) : undefined} + onDelete={ + !hideDeleteAction && f.status !== "uploading" + ? () => onDelete(f) + : undefined + } /> ))} diff --git a/src/pages/BugForm/index.tsx b/src/pages/BugForm/index.tsx index 316aeb54e..001ddad09 100644 --- a/src/pages/BugForm/index.tsx +++ b/src/pages/BugForm/index.tsx @@ -9,21 +9,27 @@ import useCampaignData from "./useCampaignData"; import Loading from "src/features/Loading"; import { useTranslation } from "react-i18next"; -export default function BugForm() { +export default function BugForm({ + shouldBeLoggedIn = true, + customRoute, +}: { + shouldBeLoggedIn?: boolean; + customRoute?: string; +}) { const { data, isError, noDevice, isFetching, campaignId } = useCampaignData(); const { t } = useTranslation(); - const route = `campaign/${campaignId}/bugform`; + const route = customRoute || `campaign/${campaignId}/bugform`; if (isFetching) { return ( - + ); } if (isError || !data?.hasBugForm) { return ( - + ); @@ -31,7 +37,7 @@ export default function BugForm() { if (noDevice) { return ( - + ); @@ -41,7 +47,7 @@ export default function BugForm() { title={t("BUGFORM_MAINTITLE", { defaultValue: "Bug form" })} heading={data ? `[CP${data.id}] - ${data.title}` : ""} route={route} - shouldBeLoggedIn + shouldBeLoggedIn={shouldBeLoggedIn} > diff --git a/src/pages/VDP/index.tsx b/src/pages/VDP/index.tsx new file mode 100644 index 000000000..a5e25f467 --- /dev/null +++ b/src/pages/VDP/index.tsx @@ -0,0 +1,21 @@ +import { useParams } from "react-router-dom"; +import { useAppSelector } from "src/store"; +import BugForm from "../BugForm"; +import Loading from "src/features/Loading"; +import { PageTemplate } from "src/features/PageTemplate"; + +export default function VdpPage() { + const { id } = useParams<{ id: string }>(); + const isPublic = useAppSelector((state) => state.publicUserPages.isPublic); + const route = `/vdp/${id}`; + + if (!isPublic) { + return ( + + + + ); + } + + return ; +} diff --git a/src/redux/publicUserPages/index.ts b/src/redux/publicUserPages/index.ts new file mode 100644 index 000000000..0986f1071 --- /dev/null +++ b/src/redux/publicUserPages/index.ts @@ -0,0 +1,33 @@ +import { createSlice } from "@reduxjs/toolkit"; + +// Define a type for the slice state +interface PublicUserState { + isPublic: boolean; + token?: string; +} + +// Define the initial state using that type +const initialState: PublicUserState = { + isPublic: false, +}; + +export const publicUserPagesSlice = createSlice({ + name: "publicUserPages", + // `createSlice` will infer the state type from the `initialState` argument + initialState, + reducers: { + setUserTokenPublic: (state, action) => { + state.token = action.payload; + state.isPublic = true; + }, + resetUserToken: (state) => { + state.token = undefined; + state.isPublic = false; + }, + }, +}); + +export const { resetUserToken, setUserTokenPublic } = + publicUserPagesSlice.actions; + +export default publicUserPagesSlice.reducer; diff --git a/src/services/tryberApi/api.ts b/src/services/tryberApi/api.ts index baadf166f..11cb0a0f9 100644 --- a/src/services/tryberApi/api.ts +++ b/src/services/tryberApi/api.ts @@ -27,7 +27,15 @@ export const api = createApi({ }); return urlps.toString(); }, - prepareHeaders: (headers) => { + prepareHeaders: (headers, { getState }) => { + const { publicUserPages } = getState() as { + publicUserPages: { isPublic: boolean; token?: string }; + }; + + if (publicUserPages.isPublic && publicUserPages.token) { + headers.set("apikey", publicUserPages.token); + } + if (process.env.REACT_APP_DEFAULT_TOKEN) { const token = process.env.REACT_APP_DEFAULT_TOKEN; headers.set("Authorization", `Bearer ${token}`); diff --git a/src/store.ts b/src/store.ts index 578ba0895..b44f03eb3 100644 --- a/src/store.ts +++ b/src/store.ts @@ -8,6 +8,7 @@ import oldReducers from "src/redux/reducer"; import bugFormReducer from "src/pages/BugForm/bugFormSlice"; import previewSelectionFormReducer from "src/pages/PreviewSelectionForm/previewSelectionFormSlice"; import userDevicesReducer from "src/pages/Devices/userDevicesSlice"; +import publicUserPageReducer from "./redux/publicUserPages"; import { useDispatch, useSelector } from "react-redux"; import type { TypedUseSelectorHook } from "react-redux"; @@ -17,6 +18,7 @@ const rootReducer = combineReducers({ previewSelectionForm: previewSelectionFormReducer, userDevices: userDevicesReducer, [tryberApi.reducerPath]: tryberApi.reducer, + publicUserPages: publicUserPageReducer, }); export function setupStore(preloadedState?: PreloadedState) {