Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
REACT_APP_FIREBASE_APP_ID: ${{ secrets.REACT_APP_FIREBASE_APP_ID }}
REACT_APP_FIREBASE_MEASUREMENT_ID: ${{ secrets.REACT_APP_FIREBASE_MEASUREMENT_ID }}
REACT_APP_ERROR_REPORTING_KEY: ${{ secrets.REACT_APP_ERROR_REPORTING_KEY }}
REACT_APP_PAYPAL_CLIENT_ID: ${{ secrets.REACT_APP_PAYPAL_CLIENT_ID }}
run: yarn build && yarn test
- name: Deploy to Firebase
uses: w9jds/firebase-action@v13.5.2
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pullrequest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ jobs:
REACT_APP_FIREBASE_APP_ID: ${{ secrets.REACT_APP_FIREBASE_APP_ID }}
REACT_APP_FIREBASE_MEASUREMENT_ID: ${{ secrets.REACT_APP_FIREBASE_MEASUREMENT_ID }}
REACT_APP_ERROR_REPORTING_KEY: ${{ secrets.REACT_APP_ERROR_REPORTING_KEY }}
REACT_APP_PAYPAL_CLIENT_ID: ${{ secrets.REACT_APP_PAYPAL_CLIENT_ID }}
run: yarn build && yarn test
15 changes: 15 additions & 0 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,21 @@ service cloud.firestore {
&& (request.auth.uid == userId);
}

match /users/v1/purchases/{userId}/histories/{historyId} {
allow write: if false;
allow read: if isAuthenticated()
&& (request.auth.uid == userId);
}

match /users/v1/purchases/{userId} {
allow create: if isAuthenticated()
&& (request.auth.uid == userId);
allow update: if false;
allow delete: if false;
allow read: if isAuthenticated()
&& (request.auth.uid == userId);
}

function isAuthenticated() {
return request.auth.uid != null;
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@mui/icons-material": "^5.15.19",
"@mui/material": "^5.15.19",
"@mui/styles": "^5.15.19",
"@paypal/react-paypal-js": "^8.8.3",
"@pdf-lib/fontkit": "^1.1.1",
"ajv": "^7.0.3",
"axios": "^0.21.1",
Expand Down
97 changes: 55 additions & 42 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import enJson from './assets/locales/en.json';
import jaJson from './assets/locales/ja.json';
import LanguageDetector from 'i18next-browser-languagedetector';
import Workbench from './components/workbench/Workbench.container';
import { PayPalScriptProvider } from '@paypal/react-paypal-js';

i18n
.use(LanguageDetector)
Expand All @@ -34,6 +35,8 @@ i18n
interpolation: { escapeValue: false },
});

const PAYPAL_CLIENT_ID = import.meta.env.REACT_APP_PAYPAL_CLIENT_ID;

class App extends React.Component<StyledComponentProps, {}> {
constructor(
props: StyledComponentProps<string> | Readonly<StyledComponentProps<string>>
Expand All @@ -58,48 +61,58 @@ class App extends React.Component<StyledComponentProps, {}> {
variantInfo: this.props.classes!.info,
}}
>
<BrowserRouter>
<Routes>
<Route path="/hid" element={<Hid />} />
<Route path="/firmware" element={<Firmware />} />
<Route path="/configure" element={<Configure />} />
<Route path="/workbench" element={<Workbench />} />
<Route
path="/keyboards"
element={<KeyboardDefinitionManagement />}
/>
<Route
path="/keyboards/:definitionId"
element={<KeyboardDefinitionManagement />}
/>
<Route path="/organizations" element={<OrganizationManagement />} />
<Route
path="/organizations/:organizationId"
element={<OrganizationManagement />}
/>
<Route path="/catalog" element={<Catalog />} />
<Route
path="/catalog/:definitionId/build"
element={<Catalog catalogDetailMode="build" />}
/>
<Route
path="/catalog/:definitionId/firmware"
element={<Catalog catalogDetailMode="firmware" />}
/>
<Route
path="/catalog/:definitionId/keymap"
element={<Catalog catalogDetailMode="keymap" />}
/>
<Route
path="/catalog/:definitionId"
element={<Catalog catalogDetailMode="introduction" />}
/>
<Route path="/docs/:docId" element={<Documents />} />
<Route path="/docs" element={<Documents />} />
<Route path="/" element={<Top />} />
<Route path="/*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
<PayPalScriptProvider
options={{
clientId: PAYPAL_CLIENT_ID,
currency: 'USD',
}}
>
<BrowserRouter>
<Routes>
<Route path="/hid" element={<Hid />} />
<Route path="/firmware" element={<Firmware />} />
<Route path="/configure" element={<Configure />} />
<Route path="/workbench" element={<Workbench />} />
<Route
path="/keyboards"
element={<KeyboardDefinitionManagement />}
/>
<Route
path="/keyboards/:definitionId"
element={<KeyboardDefinitionManagement />}
/>
<Route
path="/organizations"
element={<OrganizationManagement />}
/>
<Route
path="/organizations/:organizationId"
element={<OrganizationManagement />}
/>
<Route path="/catalog" element={<Catalog />} />
<Route
path="/catalog/:definitionId/build"
element={<Catalog catalogDetailMode="build" />}
/>
<Route
path="/catalog/:definitionId/firmware"
element={<Catalog catalogDetailMode="firmware" />}
/>
<Route
path="/catalog/:definitionId/keymap"
element={<Catalog catalogDetailMode="keymap" />}
/>
<Route
path="/catalog/:definitionId"
element={<Catalog catalogDetailMode="introduction" />}
/>
<Route path="/docs/:docId" element={<Documents />} />
<Route path="/docs" element={<Documents />} />
<Route path="/" element={<Top />} />
<Route path="/*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</PayPalScriptProvider>
</SnackbarProvider>
);
}
Expand Down
35 changes: 34 additions & 1 deletion src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
IKeySwitchOperation,
ISetupPhase,
IUserInformation,
IUserPurchase,
RootState,
SetupPhase,
} from '../store/state';
Expand Down Expand Up @@ -220,6 +221,7 @@ export const APP_UPDATE_SIGNED_IN = `${APP_ACTIONS}/SignedIn`;
export const APP_TESTED_MATRIX_CLEAR = `${APP_ACTIONS}/TestedMatrixClear`;
export const APP_TEST_MATRIX_UPDATE = `${APP_ACTIONS}/TestMatrixUpdate`;
export const APP_UPDATE_USER_INFORMATION = `${APP_ACTIONS}/UpdateUserInformation`;
export const APP_UPDATE_USER_PURCHASE = `${APP_ACTIONS}/UpdateUserPurchase`;
export const AppActions = {
updateSetupPhase: (setupPhase: ISetupPhase) => {
return {
Expand Down Expand Up @@ -367,6 +369,12 @@ export const AppActions = {
value: userInformation,
};
},
updateUserPurchase: (purchase: IUserPurchase | undefined) => {
return {
type: APP_UPDATE_USER_PURCHASE,
value: purchase,
};
},
};

type ActionTypes = ReturnType<
Expand All @@ -391,6 +399,7 @@ export const AppActionsThunk = {
const { auth } = getState();
dispatch(AppActions.updateSignedIn(false));
dispatch(AppActions.updateUserInformation(undefined));
dispatch(AppActions.updateUserPurchase(undefined));
await auth.instance!.signOut();
dispatch(await hidActionsThunk.closeOpenedKeyboard());
dispatch(AppActions.updateSetupPhase(SetupPhase.keyboardNotSelected));
Expand Down Expand Up @@ -451,7 +460,7 @@ export const AppActionsThunk = {
dispatch(NotificationActions.addError(result.error!, result.cause));
}
},
updateUserInformation: (): ThunkPromiseAction<void> => {
fetchUserInformation: (): ThunkPromiseAction<void> => {
return async (
dispatch: ThunkDispatch<RootState, undefined, ActionTypes>,
getState: () => RootState
Expand All @@ -475,6 +484,30 @@ export const AppActionsThunk = {
}
};
},
fetchUserPurchase: (): ThunkPromiseAction<void> => {
return async (
dispatch: ThunkDispatch<RootState, undefined, ActionTypes>,
getState: () => RootState
) => {
const { storage, auth, app } = getState();
if (!app.signedIn) {
dispatch(AppActions.updateUserPurchase(undefined));
return;
}
const user = auth.instance!.getCurrentAuthenticatedUserIgnoreNull();
if (user === null) {
dispatch(AppActions.updateUserPurchase(undefined));
return;
}
const result = await storage.instance!.getUserPurchase(user.uid);
if (isSuccessful(result)) {
dispatch(AppActions.updateUserPurchase(result.value));
} else {
console.error(result.cause);
dispatch(NotificationActions.addError(result.error, result.cause));
}
};
},
};

export const LAYOUT_OPTIONS_ACTIONS = '@LayoutOptions';
Expand Down
1 change: 1 addition & 0 deletions src/actions/catalog.action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export const catalogActionsThunk = {
const { auth } = getState();
dispatch(AppActions.updateSignedIn(false));
dispatch(AppActions.updateUserInformation(undefined));
dispatch(AppActions.updateUserPurchase(undefined));
await auth.instance!.signOut();
},

Expand Down
74 changes: 39 additions & 35 deletions src/actions/workbench.action.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { ThunkAction, ThunkDispatch } from 'redux-thunk';
import {
IUserInformation,

Check warning on line 3 in src/actions/workbench.action.ts

View workflow job for this annotation

GitHub Actions / Build (20.x)

'IUserInformation' is defined but never used. Allowed unused vars must match /^_/u
IWorkbenchPhase,
RootState,
WorkbenchPhase,
} from '../store/state';
import { AppActions, NotificationActions } from './actions';
import { StorageActions } from './storage.action';
import { errorResultOf, IEmptyResult, isError, successResult } from '../types';

Check warning on line 10 in src/actions/workbench.action.ts

View workflow job for this annotation

GitHub Actions / Build (20.x)

'successResult' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in src/actions/workbench.action.ts

View workflow job for this annotation

GitHub Actions / Build (20.x)

'IEmptyResult' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in src/actions/workbench.action.ts

View workflow job for this annotation

GitHub Actions / Build (20.x)

'errorResultOf' is defined but never used. Allowed unused vars must match /^_/u
import {
IBuildableFirmwareFileType,
IFirmwareBuildingTask,
IStorage,

Check warning on line 14 in src/actions/workbench.action.ts

View workflow job for this annotation

GitHub Actions / Build (20.x)

'IStorage' is defined but never used. Allowed unused vars must match /^_/u
IUserPurchaseHistory,
IWorkbenchProject,
IWorkbenchProjectFile,
} from '../services/storage/Storage';
Expand All @@ -28,6 +29,7 @@
export const WORKBENCH_APP_UPDATE_SELECTED_FILE = `${WORKBENCH_APP_ACTIONS}/UpdateSelectedFile`;
export const WORKBENCH_APP_APPEND_FILE_TO_CURRENT_PROJECT = `${WORKBENCH_APP_ACTIONS}/AppendFileToCurrentProject`;
export const WORKBENCH_APP_UPDATE_BUILDING_TASKS = `${WORKBENCH_APP_ACTIONS}/UpdateBuildableTasks`;
export const WORKBENCH_APP_UPDATE_USER_PURCHASE_HISTORIES = `${WORKBENCH_APP_ACTIONS}/UpdateUserPurchaseHistories`;
export const WorkbenchAppActions = {
updatePhase: (phase: IWorkbenchPhase) => {
return {
Expand Down Expand Up @@ -69,6 +71,14 @@
value: tasks,
};
},
updateUserPurchaseHistories: (
userPurchaseHistories: IUserPurchaseHistory[] | undefined
) => {
return {
type: WORKBENCH_APP_UPDATE_USER_PURCHASE_HISTORIES,
value: userPurchaseHistories,
};
},
};

type ActionTypes = ReturnType<
Expand Down Expand Up @@ -177,13 +187,11 @@
WorkbenchAppActions.updateCurrentProject(currentProjectWithFiles)
);

const updateUserInformationResult = await updateUserInformation(
storage.instance!,
{
const updateUserInformationResult =
await storage.instance!.updateUserInformation({
...userInformation,
currentProjectId,
}
);
});
if (isError(updateUserInformationResult)) {
dispatch(
NotificationActions.addError(
Expand Down Expand Up @@ -382,13 +390,11 @@
dispatch(WorkbenchAppActions.updateCurrentProject(newProject));
dispatch(WorkbenchAppActions.updateSelectedFile(undefined));

const updateUserInformationResult = await updateUserInformation(
storage.instance!,
{
const updateUserInformationResult =
await storage.instance!.updateUserInformation({
...app.user.information!,
currentProjectId: newProject.id,
}
);
});
if (isError(updateUserInformationResult)) {
dispatch(
NotificationActions.addError(
Expand Down Expand Up @@ -419,13 +425,11 @@
}
dispatch(WorkbenchAppActions.updateCurrentProject(currentProject));

const updateUserInformationResult = await updateUserInformation(
storage.instance!,
{
const updateUserInformationResult =
await storage.instance!.updateUserInformation({
...app.user.information!,
currentProjectId: currentProject.id,
}
);
});
if (isError(updateUserInformationResult)) {
dispatch(
NotificationActions.addError(
Expand Down Expand Up @@ -512,13 +516,11 @@
);
dispatch(WorkbenchAppActions.updateSelectedFile(undefined));

const updateUserInformationResult = await updateUserInformation(
storage.instance!,
{
const updateUserInformationResult =
await storage.instance!.updateUserInformation({
...app.user.information!,
currentProjectId: newCurrentProjectWithFiles.id,
}
);
});
if (isError(updateUserInformationResult)) {
dispatch(
NotificationActions.addError(
Expand Down Expand Up @@ -636,12 +638,29 @@
const { auth } = getState();
dispatch(AppActions.updateSignedIn(false));
dispatch(AppActions.updateUserInformation(undefined));
dispatch(AppActions.updateUserPurchase(undefined));
dispatch(WorkbenchAppActions.updateCurrentProject(undefined));
dispatch(WorkbenchAppActions.updateProjects([]));
dispatch(WorkbenchAppActions.updateSelectedFile(undefined));
dispatch(WorkbenchAppActions.updatePhase(WorkbenchPhase.processing));
await auth.instance!.signOut();
},
fetchUserPurchaseHistories: (): ThunkPromiseAction<void> => {
return async (
dispatch: ThunkDispatch<RootState, undefined, ActionTypes>,
getState: () => RootState
) => {
const { storage, auth } = getState();
const user = auth.instance!.getCurrentAuthenticatedUserIgnoreNull();
const result =
await storage.instance!.fetchRemainingBuildPurchaseHistories(user.uid);
if (isError(result)) {
dispatch(NotificationActions.addError(result.error, result.cause));
return;
}
dispatch(WorkbenchAppActions.updateUserPurchaseHistories(result.value));
};
},
};

const createDefaultProjectName = (projects: IWorkbenchProject[]): string => {
Expand Down Expand Up @@ -673,18 +692,3 @@
return 'copy';
}
};

const updateUserInformation = async (
storage: IStorage,
userInformation: IUserInformation
): Promise<IEmptyResult> => {
const updateUserInformationResult =
await storage.updateUserInformation(userInformation);
if (isError(updateUserInformationResult)) {
return errorResultOf(
updateUserInformationResult.error,
updateUserInformationResult.cause
);
}
return successResult();
};
Loading
Loading