diff --git a/src/components/Vehicles/Footer.tsx b/src/components/Vehicles/Footer.tsx
index 9341e83..a022201 100644
--- a/src/components/Vehicles/Footer.tsx
+++ b/src/components/Vehicles/Footer.tsx
@@ -1,37 +1,69 @@
import React from 'react';
import PrimaryButton from '../Shared/PrimaryButton';
+interface FooterProps {
+ canShare: boolean;
+ onCancel: () => void;
+ onShare: () => void;
+ selectedVehiclesCount: number;
+ onUpdatePermissions: () => void;
+ hasVehicleWithOldPermissions: boolean;
+}
+
const Footer = ({
canShare,
onCancel,
onShare,
selectedVehiclesCount,
-}: {
- canShare: boolean;
- onCancel: () => void;
- onShare: () => void;
- selectedVehiclesCount: number;
-}) => {
+ onUpdatePermissions,
+ hasVehicleWithOldPermissions,
+}: FooterProps) => {
+ const showContinueButton = !canShare;
+ const showUpdatePermissionsButton = hasVehicleWithOldPermissions && canShare;
+ const showShareButtons = !hasVehicleWithOldPermissions && canShare;
+
+ const renderContinueButton = () => {
+ return (
+
+ );
+ };
+
+ const renderCancelAndShareButtons = () => {
+ return (
+
+
+ Cancel
+
+
+ Save changes
+
+
+ );
+ };
+
+ const renderUpdatePermissionsButton = () => {
+ return (
+
+
+ There are vehicles with old permissions, update them before continuing
+
+
+ Update vehicles permissions
+
+
+ );
+ };
+
return (
-
- {!canShare &&
Continue }
- {canShare && (
- <>
-
- Cancel
-
-
- Save changes
-
- >
- )}
+
+ {showContinueButton && renderContinueButton()}
+ {showUpdatePermissionsButton && renderUpdatePermissionsButton()}
+ {showShareButtons && renderCancelAndShareButtons()}
);
};
diff --git a/src/components/Vehicles/ManageVehicleDetails.tsx b/src/components/Vehicles/ManageVehicleDetails.tsx
index 9fd38df..1273e03 100644
--- a/src/components/Vehicles/ManageVehicleDetails.tsx
+++ b/src/components/Vehicles/ManageVehicleDetails.tsx
@@ -3,28 +3,9 @@ import React from 'react';
import { Vehicle } from '../../models/vehicle';
import Header from '../Shared/Header';
import { SharedPermissionsNote } from './';
-import { useDevCredentials } from '../../context/DevCredentialsContext';
-import { VehicleManagerMandatoryParams } from '../../types';
-import { hasUpdatedPermissions } from '../../utils/permissions';
export const ManageVehicleDetails = ({ vehicle }: { vehicle: Vehicle }) => {
- const { permissions, permissionTemplateId } =
- useDevCredentials
();
-
- const {
- tokenId,
- shared,
- expiresAt,
- make,
- model,
- year,
- permissions: vehiclePermissions,
- } = vehicle;
- const hasUpdatedPerms = hasUpdatedPermissions(
- vehiclePermissions,
- permissions,
- permissionTemplateId,
- );
+ const { tokenId, shared, expiresAt, make, model, year, hasOldPermissions } = vehicle;
return (
<>
@@ -41,7 +22,7 @@ export const ManageVehicleDetails = ({ vehicle }: { vehicle: Vehicle }) => {
Shared until {expiresAt}
-
+
>
);
};
diff --git a/src/components/Vehicles/SelectVehicles.tsx b/src/components/Vehicles/SelectVehicles.tsx
index 6afc11f..6d240ae 100644
--- a/src/components/Vehicles/SelectVehicles.tsx
+++ b/src/components/Vehicles/SelectVehicles.tsx
@@ -23,6 +23,7 @@ export const SelectVehicles: React.FC = () => {
incompatibleVehicles,
hasNextPage,
hasPreviousPage,
+ hasVehicleWithOldPermissions,
} = useFetchVehicles();
const {
selectedVehicles,
@@ -68,6 +69,23 @@ export const SelectVehicles: React.FC = () => {
}
};
+ const handleUpdatePermissions = async () => {
+ try {
+ setLoadingState(true, 'Updating vehicles permissions', true);
+ const vehiclesWithOldPermissions = vehicles.filter(
+ ({ hasOldPermissions }) => hasOldPermissions,
+ );
+ await handleShareVehicles(vehiclesWithOldPermissions);
+ } catch (err) {
+ captureException(err);
+ if (!isInvalidSessionError(err)) {
+ setError('Failed to update vehicles permissions');
+ }
+ } finally {
+ setLoadingState(false);
+ }
+ };
+
const onCancel = () => {
finishShareVehicles([]);
};
@@ -121,6 +139,8 @@ export const SelectVehicles: React.FC = () => {
onCancel={onCancel}
onShare={handleShare}
selectedVehiclesCount={selectedVehicles.length}
+ onUpdatePermissions={handleUpdatePermissions}
+ hasVehicleWithOldPermissions={hasVehicleWithOldPermissions}
/>
>
diff --git a/src/components/Vehicles/SharedPermissionsNote.tsx b/src/components/Vehicles/SharedPermissionsNote.tsx
index 828c754..e123a30 100644
--- a/src/components/Vehicles/SharedPermissionsNote.tsx
+++ b/src/components/Vehicles/SharedPermissionsNote.tsx
@@ -2,21 +2,21 @@ import React from 'react';
interface SharedPermissionsNoteProps {
shared: boolean;
- hasUpdatedPermissions: boolean;
+ hasOldPermissions: boolean;
}
export const SharedPermissionsNote: React.FC = ({
shared,
- hasUpdatedPermissions,
+ hasOldPermissions,
}) => {
- if (!shared || hasUpdatedPermissions) {
+ if (!shared || !hasOldPermissions) {
return null;
}
return (
- Note: Shared with old permissions, revoke and
- re-share to ensure service continuity
+ Note: Shared with old permissions, update
+ them to ensure service continuity
);
};
diff --git a/src/components/Vehicles/VehicleCard.tsx b/src/components/Vehicles/VehicleCard.tsx
index f82e82b..46f0783 100644
--- a/src/components/Vehicles/VehicleCard.tsx
+++ b/src/components/Vehicles/VehicleCard.tsx
@@ -4,10 +4,7 @@ import { Vehicle } from '../../models/vehicle';
import { UiStates } from '../../enums';
import { useUIManager } from '../../context/UIManagerContext';
import { Checkbox } from '../Shared/Checkbox';
-import { useDevCredentials } from '../../context/DevCredentialsContext';
-import { VehicleManagerMandatoryParams } from '../../types';
import { SharedPermissionsNote } from './';
-import { hasUpdatedPermissions } from '../../utils/permissions';
interface VehicleCardProps {
vehicle: Vehicle;
@@ -25,23 +22,8 @@ export const VehicleCard: React.FC = ({
incompatible,
}) => {
const { componentData, setComponentData, setUiState } = useUIManager();
- const { permissions, permissionTemplateId } =
- useDevCredentials();
- const {
- tokenId,
- shared,
- model,
- make,
- year,
- expiresAt,
- permissions: vehiclePermissions,
- } = vehicle;
- const hasUpdatedPerms = hasUpdatedPermissions(
- vehiclePermissions,
- permissions,
- permissionTemplateId,
- );
+ const { tokenId, shared, model, make, year, expiresAt, hasOldPermissions } = vehicle;
const handleManageClick = (e: React.MouseEvent) => {
setComponentData({ ...componentData, vehicle }); //Retains permissionTemplateID for Manage Vehicle
@@ -103,7 +85,7 @@ export const VehicleCard: React.FC = ({
ID: {tokenId.toString()}
{shared && Shared Until: {expiresAt}
}
-
+
{/* Manage Vehicle */}
diff --git a/src/hooks/useFetchVehicles.ts b/src/hooks/useFetchVehicles.ts
index 0712dd5..94e4633 100644
--- a/src/hooks/useFetchVehicles.ts
+++ b/src/hooks/useFetchVehicles.ts
@@ -7,14 +7,21 @@ import { VehicleManagerMandatoryParams } from '../types';
export const useFetchVehicles = () => {
const { user } = useAuthContext();
- const { clientId, vehicleTokenIds, vehicleMakes, powertrainTypes } =
- useDevCredentials();
+ const {
+ clientId,
+ vehicleTokenIds,
+ vehicleMakes,
+ powertrainTypes,
+ permissionTemplateId,
+ permissions,
+ } = useDevCredentials();
const [startCursor, setStartCursor] = useState('');
const [endCursor, setEndCursor] = useState('');
const [hasNextPage, setHasNextPage] = useState(false);
const [hasPreviousPage, setHasPreviousPage] = useState(false);
const [vehicles, setVehicles] = useState([]);
const [incompatibleVehicles, setIncompatibleVehicles] = useState([]);
+ const [hasVehicleWithOldPermissions, setHasVehicleWithOldPermissions] = useState(false);
const fetchVehicles = async (direction = 'next') => {
const cursor = direction === 'next' ? endCursor : startCursor;
@@ -28,9 +35,12 @@ export const useFetchVehicles = () => {
vehicleMakes,
powertrainTypes,
},
+ permissionTemplateId,
+ permissions,
});
setVehicles(transformedVehicles.compatibleVehicles);
setIncompatibleVehicles(transformedVehicles.incompatibleVehicles);
+ setHasVehicleWithOldPermissions(transformedVehicles.hasVehicleWithOldPermissions);
setEndCursor(transformedVehicles.endCursor);
setStartCursor(transformedVehicles.startCursor);
setHasPreviousPage(transformedVehicles.hasPreviousPage);
@@ -43,6 +53,7 @@ export const useFetchVehicles = () => {
hasPreviousPage,
vehicles,
incompatibleVehicles,
+ hasVehicleWithOldPermissions,
};
};
diff --git a/src/models/__tests__/vehicle.test.ts b/src/models/__tests__/vehicle.test.ts
index 37dd9b2..5d2417e 100644
--- a/src/models/__tests__/vehicle.test.ts
+++ b/src/models/__tests__/vehicle.test.ts
@@ -9,6 +9,7 @@ jest.mock('@dimo-network/transactions', () => ({
describe('LocalVehicle', () => {
const mockVehicleNode = {
tokenId: 123,
+ tokenDID: 'did:erc721:0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF:123',
imageURI: 'http://image.url',
definition: {
id: 'mock_def_id',
@@ -49,6 +50,7 @@ describe('LocalVehicle', () => {
it('normalizes vehicle data', () => {
expect(localVehicle.normalize()).toEqual({
tokenId: 123,
+ tokenDID: 'did:erc721:0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF:123',
imageURI: 'http://image.url',
make: 'Tesla',
model: 'Model S',
diff --git a/src/models/vehicle.ts b/src/models/vehicle.ts
index 1a11747..9109eb0 100644
--- a/src/models/vehicle.ts
+++ b/src/models/vehicle.ts
@@ -4,6 +4,7 @@ import { fetchDeviceDefinition, VehicleNode } from '../services';
export interface Vehicle {
tokenId: number;
+ tokenDID: string;
imageURI: string;
make: string;
model: string;
@@ -11,11 +12,13 @@ export interface Vehicle {
shared: boolean;
expiresAt: string;
permissions: string;
+ hasOldPermissions: boolean;
}
export interface VehicleResponse {
compatibleVehicles: Vehicle[];
incompatibleVehicles: Vehicle[];
+ hasVehicleWithOldPermissions: boolean;
hasNextPage: boolean;
endCursor: string;
hasPreviousPage: boolean;
@@ -67,6 +70,7 @@ export class LocalVehicle {
normalize() {
return {
tokenId: this.tokenId,
+ tokenDID: this.vehicleNode.tokenDID,
imageURI: this.vehicleNode.imageURI,
make: this.make,
model: this.model,
diff --git a/src/services/__tests__/vehicleService.test.ts b/src/services/__tests__/vehicleService.test.ts
index c6a6e01..e015ae8 100644
--- a/src/services/__tests__/vehicleService.test.ts
+++ b/src/services/__tests__/vehicleService.test.ts
@@ -21,6 +21,7 @@ beforeEach(() => {
nodes: [
{
tokenId: 1,
+ tokenDID: 'did:erc721:0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF:1',
imageURI: 'http://image.url',
definition: {
id: 'tesla_model_3_2019',
@@ -34,6 +35,7 @@ beforeEach(() => {
},
{
tokenId: 2,
+ tokenDID: 'did:erc721:0xbA5738a18d83D41847dfFbDC6101d37C69c9B0cF:2',
imageURI: 'http://image.url',
definition: {
id: 'ford_bronco_2023',
@@ -177,6 +179,7 @@ it('Returns vehicles with the correct shape in compatible and incompatible array
expect(vehicle).toEqual(
expect.objectContaining({
tokenId: expect.any(Number),
+ tokenDID: expect.any(String),
imageURI: expect.anything(),
shared: expect.any(Boolean),
expiresAt: expect.any(String),
@@ -201,6 +204,7 @@ it('Returns incompatible vehicles with the correct shape when no compatible vehi
expect(vehicle).toEqual(
expect.objectContaining({
tokenId: expect.any(Number),
+ tokenDID: expect.any(String),
imageURI: expect.anything(),
shared: expect.any(Boolean),
expiresAt: expect.any(String),
diff --git a/src/services/identityService.ts b/src/services/identityService.ts
index 1413124..7a2be67 100644
--- a/src/services/identityService.ts
+++ b/src/services/identityService.ts
@@ -35,6 +35,7 @@ const GET_VEHICLES = gql`
) {
nodes {
tokenId
+ tokenDID
imageURI
definition {
id
@@ -62,6 +63,7 @@ const GET_VEHICLES = gql`
export type VehicleNode = {
tokenId: number;
+ tokenDID: string;
imageURI: string;
definition: {
id: string;
diff --git a/src/services/vehicleService.ts b/src/services/vehicleService.ts
index a6e786a..e097cd3 100644
--- a/src/services/vehicleService.ts
+++ b/src/services/vehicleService.ts
@@ -3,10 +3,23 @@ import { fetchVehicles } from './identityService';
import { IParams } from '../types';
import { sortVehiclesByFilters, transformVehicles } from '../utils/vehicles';
+interface FetchVehiclesWithTransformationParams extends IParams {
+ permissionTemplateId: string;
+ permissions: string;
+}
+
export const fetchVehiclesWithTransformation = async (
- params: IParams,
+ params: FetchVehiclesWithTransformationParams,
): Promise => {
- const { ownerAddress, targetGrantee, cursor, direction, filters = {} } = params;
+ const {
+ ownerAddress,
+ targetGrantee,
+ cursor,
+ direction,
+ filters = {},
+ permissionTemplateId,
+ permissions,
+ } = params;
const {
data: {
@@ -19,12 +32,32 @@ export const fetchVehiclesWithTransformation = async (
filters,
);
+ const transformedCompatibleVehicles = transformVehicles({
+ vehicles: compatibleVehicles,
+ grantee: targetGrantee,
+ permissionTemplateId,
+ permissions,
+ });
+
+ const transformedIncompatibleVehicles = transformVehicles({
+ vehicles: incompatibleVehicles,
+ grantee: targetGrantee,
+ permissionTemplateId,
+ permissions,
+ });
+
+ const hasVehicleWithOldPermissions = [
+ ...transformedCompatibleVehicles,
+ ...transformedIncompatibleVehicles,
+ ].some((vehicle) => vehicle.hasOldPermissions);
+
return {
hasNextPage: pageInfo.hasNextPage,
hasPreviousPage: pageInfo.hasPreviousPage,
startCursor: pageInfo.startCursor || '',
endCursor: pageInfo.endCursor || '',
- compatibleVehicles: transformVehicles(compatibleVehicles, targetGrantee),
- incompatibleVehicles: transformVehicles(incompatibleVehicles, targetGrantee),
+ compatibleVehicles: transformedCompatibleVehicles,
+ incompatibleVehicles: transformedIncompatibleVehicles,
+ hasVehicleWithOldPermissions,
};
};
diff --git a/src/utils/vehicles.ts b/src/utils/vehicles.ts
index ef23f06..bfef311 100644
--- a/src/utils/vehicles.ts
+++ b/src/utils/vehicles.ts
@@ -1,26 +1,59 @@
import { VehicleFilters, VehiclePermissionsAction } from '../types';
import { LocalVehicle, Vehicle } from '../models/vehicle';
import { extendByYear, formatDate, parseExpirationDate } from './dateUtils';
+import { hasUpdatedPermissions } from './permissions';
-const transformVehicle = (
- vehicle: LocalVehicle,
- grantee: `0x${string}` | null,
-): Vehicle => {
+interface TransformVehicleParams {
+ vehicle: LocalVehicle;
+ grantee: `0x${string}` | null;
+ permissionTemplateId: string;
+ permissions: string;
+}
+
+const transformVehicle = ({
+ vehicle,
+ grantee,
+ permissionTemplateId,
+ permissions,
+}: TransformVehicleParams): Vehicle => {
const sacd = vehicle.getSacdForGrantee(grantee);
+ const vehiclePermissions = sacd ? sacd.permissions : '0';
+ const updatedPermissions = hasUpdatedPermissions(
+ vehiclePermissions,
+ permissions,
+ permissionTemplateId,
+ );
return {
...vehicle.normalize(),
- permissions: sacd ? sacd.permissions : '0',
+ permissions: vehiclePermissions,
shared: !!sacd,
expiresAt: sacd ? formatDate(sacd.expiresAt) : '',
+ hasOldPermissions: updatedPermissions,
};
};
-export const transformVehicles = (
- vehicles: LocalVehicle[],
- grantee: `0x${string}` | null,
-) => {
+interface TransformVehiclesParams {
+ vehicles: LocalVehicle[];
+ grantee: `0x${string}` | null;
+ permissionTemplateId: string;
+ permissions: string;
+}
+
+export const transformVehicles = ({
+ vehicles,
+ grantee,
+ permissionTemplateId,
+ permissions,
+}: TransformVehiclesParams) => {
return vehicles
- .map((vehicle) => transformVehicle(vehicle, grantee))
+ .map((vehicle) =>
+ transformVehicle({
+ vehicle,
+ grantee,
+ permissionTemplateId,
+ permissions,
+ }),
+ )
.sort((a: any, b: any) => Number(b.shared) - Number(a.shared));
};