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 && (
+
+
(Link to bugs page):::BUGFORM_CONFIRMUPLOAD_TXT"
+ }
+ components={{
+ bugs_link: (
+
+ ),
+ }}
+ defaults="Go to the Bugs page to check all the bugs you have uploaded."
+ />
+
+ )}
,
"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) {