@salesforce/webapp-template-app-react-sample-b2x-experimental 1.116.9 → 1.116.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,13 @@
1
+ query TenantAccess($userId: ID!) {
2
+ uiapi {
3
+ query {
4
+ Tenant__c(where: { User__c: { eq: $userId } }, first: 1) {
5
+ edges {
6
+ node {
7
+ Id
8
+ }
9
+ }
10
+ }
11
+ }
12
+ }
13
+ }
@@ -0,0 +1,12 @@
1
+ import TENANT_ACCESS_QUERY from "./query/tenantAccess.graphql?raw";
2
+ import type { TenantAccessQuery, TenantAccessQueryVariables } from "./graphql-operations-types.js";
3
+ import { executeGraphQL } from "@/api/graphqlClient.js";
4
+
5
+ export async function hasTenantAccess(userId: string): Promise<boolean> {
6
+ if (!userId.trim()) return false;
7
+ const response = await executeGraphQL<TenantAccessQuery, TenantAccessQueryVariables>(
8
+ TENANT_ACCESS_QUERY,
9
+ { userId },
10
+ );
11
+ return Boolean(response.uiapi?.query?.Tenant__c?.edges?.[0]?.node?.Id);
12
+ }
@@ -1,6 +1,7 @@
1
1
  import { Link, useLocation } from "react-router";
2
2
  import { Home, Search, BarChart3, Wrench, Phone, type LucideIcon } from "lucide-react";
3
3
  import { useAuth } from "@/features/authentication/context/AuthContext";
4
+ import { useTenantAccess } from "@/hooks/useTenantAccess";
4
5
  import { useMemo } from "react";
5
6
 
6
7
  interface NavItem {
@@ -25,11 +26,20 @@ interface NavMenuProps {
25
26
 
26
27
  export function NavMenu({ isOpen = false, onClose }: NavMenuProps) {
27
28
  const location = useLocation();
28
- const { isAuthenticated } = useAuth();
29
+ const { isAuthenticated, user } = useAuth();
30
+ const { hasTenantRecord } = useTenantAccess(user?.id);
29
31
 
30
32
  const visibleItems = useMemo(
31
- () => navItems.filter((item) => !item.authRequired || isAuthenticated),
32
- [isAuthenticated],
33
+ () =>
34
+ navItems.filter((item) => {
35
+ if (!item.authRequired) return true;
36
+ if (!isAuthenticated) return false;
37
+ if (item.path === "/dashboard" || item.path === "/maintenance") {
38
+ return hasTenantRecord;
39
+ }
40
+ return true;
41
+ }),
42
+ [hasTenantRecord, isAuthenticated],
33
43
  );
34
44
 
35
45
  const isActive = (path: string) => {
@@ -0,0 +1,22 @@
1
+ import { Navigate, Outlet, useLocation } from "react-router";
2
+ import { useAuth } from "@/features/authentication/context/AuthContext";
3
+ import { ROUTES } from "@/features/authentication/authenticationConfig";
4
+ import { useTenantAccess } from "@/hooks/useTenantAccess";
5
+
6
+ export default function TenantRoute() {
7
+ const location = useLocation();
8
+ const { isAuthenticated, loading, user } = useAuth();
9
+ const { hasTenantRecord, loading: tenantLoading } = useTenantAccess(user?.id);
10
+
11
+ if (loading || tenantLoading) return null;
12
+
13
+ if (!isAuthenticated) {
14
+ return <Navigate to={ROUTES.LOGIN.PATH} state={{ from: location.pathname }} replace />;
15
+ }
16
+
17
+ if (!hasTenantRecord) {
18
+ return <Navigate to="/" replace />;
19
+ }
20
+
21
+ return <Outlet />;
22
+ }
@@ -0,0 +1,38 @@
1
+ import { useEffect, useState } from "react";
2
+ import { hasTenantAccess } from "@/api/tenantApi";
3
+
4
+ export function useTenantAccess(userId: string | undefined): {
5
+ hasTenantRecord: boolean;
6
+ loading: boolean;
7
+ } {
8
+ const [hasTenantRecord, setHasTenantRecord] = useState(false);
9
+ const [loading, setLoading] = useState(false);
10
+
11
+ useEffect(() => {
12
+ const id = userId?.trim() ?? "";
13
+ if (!id) {
14
+ setHasTenantRecord(false);
15
+ setLoading(false);
16
+ return;
17
+ }
18
+
19
+ let cancelled = false;
20
+ setLoading(true);
21
+ hasTenantAccess(id)
22
+ .then((allowed) => {
23
+ if (!cancelled) setHasTenantRecord(allowed);
24
+ })
25
+ .catch(() => {
26
+ if (!cancelled) setHasTenantRecord(false);
27
+ })
28
+ .finally(() => {
29
+ if (!cancelled) setLoading(false);
30
+ });
31
+
32
+ return () => {
33
+ cancelled = true;
34
+ };
35
+ }, [userId]);
36
+
37
+ return { hasTenantRecord, loading };
38
+ }
@@ -31,21 +31,21 @@ import type { FilterFieldConfig } from "@/features/object-search/utils/filterUti
31
31
  import type { SortFieldConfig } from "@/features/object-search/utils/sortUtils";
32
32
 
33
33
  const TYPE_OPTIONS = [
34
- "Plumbing",
35
- "Electrical",
36
- "HVAC",
37
- "Appliance",
38
- "Structural",
39
- "Cleaning",
40
- "Security",
41
- "Pest",
42
- "Other",
34
+ { value: "Plumbing", label: "Plumbing" },
35
+ { value: "Electrical", label: "Electrical" },
36
+ { value: "HVAC", label: "HVAC" },
37
+ { value: "Appliance", label: "Appliance" },
38
+ { value: "Carpentry", label: "Carpentry" },
39
+ { value: "Landscaping", label: "Landscaping" },
40
+ { value: "Cleaning", label: "Cleaning" },
41
+ { value: "Pest", label: "Pest Control" },
42
+ { value: "Other", label: "Other" },
43
43
  ] as const;
44
44
 
45
45
  const PRIORITY_OPTIONS = [
46
46
  { value: "Standard", label: "Standard" },
47
- { value: "High", label: "High (Same Day)" },
48
- { value: "Emergency", label: "Emergency (2hr)" },
47
+ { value: "High (Same Day)", label: "High (Same Day)" },
48
+ { value: "Emergency (2hr)", label: "Emergency (2hr)" },
49
49
  ] as const;
50
50
 
51
51
  const FILTER_CONFIGS: FilterFieldConfig[] = [];
@@ -253,8 +253,8 @@ export default function Maintenance() {
253
253
  >
254
254
  <option value="">—</option>
255
255
  {TYPE_OPTIONS.map((o) => (
256
- <option key={o} value={o}>
257
- {o}
256
+ <option key={o.value} value={o.value}>
257
+ {o.label}
258
258
  </option>
259
259
  ))}
260
260
  </select>
@@ -19,6 +19,7 @@ import PropertySearch from "@/pages/PropertySearch";
19
19
  import PropertyDetails from "@/pages/PropertyDetails";
20
20
  import Application from "@/pages/Application";
21
21
  import Contact from "@/pages/Contact";
22
+ import TenantRoute from "./features/authentication/layouts/TenantRoute";
22
23
 
23
24
  export const routes: RouteObject[] = [
24
25
  {
@@ -102,6 +103,15 @@ export const routes: RouteObject[] = [
102
103
  },
103
104
  {
104
105
  element: <PrivateRoute />,
106
+ children: [
107
+ {
108
+ path: "application",
109
+ element: <Application />
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ element: <TenantRoute />,
105
115
  children: [
106
116
  {
107
117
  path: "dashboard",
@@ -112,10 +122,6 @@ export const routes: RouteObject[] = [
112
122
  path: "maintenance",
113
123
  element: <Maintenance />,
114
124
  handle: { showInNavigation: true, label: "Maintenance" }
115
- },
116
- {
117
- path: "application",
118
- element: <Application />
119
125
  }
120
126
  ]
121
127
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.116.9",
3
+ "version": "1.116.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
9
- "version": "1.116.9",
9
+ "version": "1.116.10",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "devDependencies": {
12
12
  "@lwc/eslint-plugin-lwc": "^3.3.0",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-base-sfdx-project-experimental",
3
- "version": "1.116.9",
3
+ "version": "1.116.10",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/webapp-template-app-react-sample-b2x-experimental",
3
- "version": "1.116.9",
3
+ "version": "1.116.10",
4
4
  "description": "Salesforce sample property rental React app",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",