Skip to content

Commit

Permalink
fixes regarding UI reactiveness
Browse files Browse the repository at this point in the history
  • Loading branch information
iresharma committed Aug 29, 2023
1 parent 1b80a92 commit 8e3b70e
Show file tree
Hide file tree
Showing 16 changed files with 122 additions and 77 deletions.
2 changes: 1 addition & 1 deletion build/static/css/main.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/static/css/main.css.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/static/js/bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion build/static/js/bundle.js.map

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"semver": "^7.3.5",
"source-map-loader": "^3.0.0",
"style-loader": "^3.3.1",
"supertokens-node": "^15.0.2",
"supertokens-node": "^15.1.0",
"tailwindcss": "^3.0.2",
"terser-webpack-plugin": "^5.2.5",
"web-vitals": "^2.1.4",
Expand Down
22 changes: 22 additions & 0 deletions server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import Passwordless from "supertokens-node/recipe/passwordless";
import Session from "supertokens-node/recipe/session";
import UserMetaData from "supertokens-node/recipe/usermetadata";
import ThirdParty from "supertokens-node/recipe/thirdparty";
import AccountLinking from "supertokens-node/recipe/accountlinking";
import RecipeUserId from "../../supertokens-node/lib/build/recipeUserId";

const websiteDomain = "http://localhost:3000";

Expand Down Expand Up @@ -86,6 +88,7 @@ SuperTokens.init({
mode: "REQUIRED",
}),
Session.init(),
AccountLinking.init(),
],
});

Expand All @@ -105,6 +108,25 @@ app.get("/status", (req, res) => {
res.status(200).send("Started");
});

app.get("/link", async (req, res) => {
await AccountLinking.linkAccounts(
"public",
new RecipeUserId("6b763048-486f-4965-b2e0-2f7650efbdf5"),
"6f922cbf-99de-4078-a9d0-e67dff5df09d"
);
await AccountLinking.linkAccounts(
"public",
new RecipeUserId("9a8837c0-ee02-457b-93bd-61bf16a6c2f9"),
"6f922cbf-99de-4078-a9d0-e67dff5df09d"
);
await AccountLinking.linkAccounts(
"public",
new RecipeUserId("a31e669f-553a-40dc-9192-1b06c9d75d31"),
"6f922cbf-99de-4078-a9d0-e67dff5df09d"
);
return res.status(200).send("OK");
});

app.use((err: any, req: Request, res: Response, next: NextFunction) => {
// Leaving this in because it helps with debugging
console.log("Internal error", err);
Expand Down
7 changes: 5 additions & 2 deletions src/ui/components/layout/layoutPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ export type LayoutPanelProps = {
children?: React.ReactNode;
header?: React.ReactNode;
className?: string;
headerBorder?: boolean;
};

export const LayoutPanel: FC<LayoutPanelProps> = ({ children, header, className }) => {
export const LayoutPanel: FC<LayoutPanelProps> = ({ children, header, className, headerBorder }) => {
return (
<div className={`panel ${className ?? ""}`}>
{header !== undefined && (
<div className={`panel__header ${children !== undefined ? "with-border" : ""}`}>{header}</div>
<div className={`panel__header ${children !== undefined && headerBorder ? "with-border" : ""}`}>
{header}
</div>
)}
{children !== undefined && <div className="panel__body">{children}</div>}
</div>
Expand Down
52 changes: 28 additions & 24 deletions src/ui/components/userDetail/loginMethods/LoginMethods.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import useDeleteUserService from "../../../../api/user/delete";
import useUnlinkService from "../../../../api/user/unlink";
import useUserService, { type IUpdateUserInformationArgs, UpdateUserInformationResponse } from "../../../../api/user";
import useVerifyUserTokenService from "../../../../api/user/email/verify/token";
import { formatLongDate } from "../../../../utils/index";

const UserRecipeTypeText: Record<UserRecipeType, string> = {
["emailpassword"]: "Email password",
Expand Down Expand Up @@ -96,6 +97,7 @@ type MethodProps = {
refetchAllData: () => Promise<void>;
updateContext: (val: LoginMethod, ind: number) => void;
index: number;
showUnlink: boolean;
};

const Methods: React.FC<MethodProps> = ({
Expand All @@ -107,24 +109,11 @@ const Methods: React.FC<MethodProps> = ({
refetchAllData,
updateContext,
index,
showUnlink,
}) => {
const { sendUserEmailVerification: sendUserEmailVerificationApi } = useVerifyUserTokenService();
const { showModal, showToast } = useContext(PopupContentContext);
const [emailError, setEmailError] = useState("");
const dateToWord = (timestamp: number) => {
const date = new Date(timestamp);
return (
date.getUTCDay() +
"th " +
date.getUTCMonth() +
", " +
date.getFullYear() +
" at " +
date.getUTCHours() +
":" +
date.getMinutes()
);
};

const trim = (val: string) => {
const len = val.length;
Expand Down Expand Up @@ -197,6 +186,7 @@ const Methods: React.FC<MethodProps> = ({
updateContext(tempLoginMethod, index);
setEdit(false);
}
await refetchAllData();
};
return (
<div className="method">
Expand All @@ -219,14 +209,25 @@ const Methods: React.FC<MethodProps> = ({
onEdit={() => setEdit(!isEditing)}
onDelete={onDeleteCallback}
onUnlink={onUnlinkCallback}
showUnlink={showUnlink}
/>
)}
{isEditing && (
<button
onClick={updateUser}
className="save-button">
Save
</button>
<>
<button
onClick={async () => {
await refetchAllData();
setEdit(false);
}}
className="button button-error-outline cancel">
Cancel
</button>
<button
onClick={updateUser}
className="save-button">
Save
</button>
</>
)}
</div>
</div>
Expand Down Expand Up @@ -264,7 +265,7 @@ const Methods: React.FC<MethodProps> = ({
{loginMethod.recipeId === "thirdparty" && (
<>
<div>
Created On:<b>{dateToWord(loginMethod.timeJoined)}</b>
Created On:<b>{formatLongDate(loginMethod.timeJoined)}</b>
</div>
<div>
{loginMethod.thirdParty && (
Expand All @@ -278,11 +279,11 @@ const Methods: React.FC<MethodProps> = ({
{loginMethod.recipeId === "passwordless" && (
<>
<div>
Created On: <b>{dateToWord(loginMethod.timeJoined)}</b>
Created On: <b>{formatLongDate(loginMethod.timeJoined)}</b>
</div>
<div>
<EditableInput
label={"Phonenumber"}
label={"Phone Number"}
val={loginMethod.phoneNumber ?? ""}
edit={isEditing}
type={"phone"}
Expand All @@ -302,7 +303,7 @@ const Methods: React.FC<MethodProps> = ({
</span>
</div>
<div>
Created On: <b>{dateToWord(loginMethod.timeJoined)}</b>
Created On: <b>{formatLongDate(loginMethod.timeJoined)}</b>
</div>
</>
)}
Expand Down Expand Up @@ -386,7 +387,9 @@ export const LoginMethods: React.FC<LoginMethodProps> = ({ refetchAllData }) =>
setUserDetails({ ...tempUserDetails });
};
return (
<LayoutPanel header={<div className="title">Login Methods</div>}>
<LayoutPanel
headerBorder={false}
header={<div className="title">Login Methods</div>}>
{methods.map((val, ind) => (
<Methods
loginMethod={val}
Expand All @@ -398,6 +401,7 @@ export const LoginMethods: React.FC<LoginMethodProps> = ({ refetchAllData }) =>
refetchAllData={refetchAllData}
updateContext={updateLoginMethodInContext}
index={ind}
showUnlink={methods.length > 1}
/>
))}
</LayoutPanel>
Expand Down
19 changes: 11 additions & 8 deletions src/ui/components/userDetail/loginMethods/components/dropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ type DropDOwnProps = {
onEdit: () => void;
onUnlink: () => void | null;
onDelete: () => void | null;
showUnlink: boolean;
};

export const DropDown = ({ onEdit, onUnlink, onDelete }: DropDOwnProps) => {
export const DropDown = ({ onEdit, onUnlink, onDelete, showUnlink }: DropDOwnProps) => {
const [open, setOpen] = useState(false);
const [hover, setHover] = useState(false);
const ref = useRef(null);
Expand Down Expand Up @@ -37,13 +38,15 @@ export const DropDown = ({ onEdit, onUnlink, onDelete }: DropDOwnProps) => {
/>{" "}
Edit
</div>
<div onClick={() => close(onUnlink)}>
<img
src={getImageUrl("unlink-login-method.png")}
alt=""
/>{" "}
Unlink
</div>
{showUnlink && (
<div onClick={() => close(onUnlink)}>
<img
src={getImageUrl("unlink-login-method.png")}
alt=""
/>{" "}
Unlink
</div>
)}
<div onClick={() => close(onDelete)}>
<img
src={getImageUrl("delete-login-method.png")}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import InputField from "../../../inputField/InputField";
import "./editableInput.scss";
import { PhoneNumberInput } from "../../../phoneNumber/PhoneNumberInput";
Expand All @@ -14,11 +14,10 @@ export type EditableInputProps = {
};

export const EditableInput = ({ label, val, edit, type, onChange, error }: EditableInputProps) => {
const [normaliseVal, setVal] = useState(val);
if (!edit) {
return (
<span>
{label}:<b>{normaliseVal == "" ? "-" : normaliseVal}</b>
{label}:<b>{val == "" ? "-" : val}</b>
</span>
);
} else {
Expand All @@ -31,9 +30,7 @@ export const EditableInput = ({ label, val, edit, type, onChange, error }: Edita
name="email"
error={error ?? ""}
value={val}
// handleChange={({ target: { value } }) => updateUserDataState({ emails: [value] })}
handleChange={({ target }) => {
setVal(target.value);
onChange(target.value);
}}
/>
Expand All @@ -42,7 +39,7 @@ export const EditableInput = ({ label, val, edit, type, onChange, error }: Edita
<PhoneNumberInput
value={val}
name="Phone Number"
onChange={setVal}
onChange={onChange}
/>
)}
</span>
Expand Down
6 changes: 6 additions & 0 deletions src/ui/components/userDetail/loginMethods/loginMethods.scss
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,9 @@
width: 15px;
}
}

.cancel {
margin: 0.25rem !important;
padding: 6px 12px !important;
font-weight: 500;
}
20 changes: 11 additions & 9 deletions src/ui/components/userDetail/userDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const UserDetail: React.FC<UserDetailProps> = (props) => {

const loadUserDetail = useCallback(async () => {
const userDetailsResponse = await getUser(user);
setUserDetail(userDetailsResponse);
setUserDetail(JSON.parse(JSON.stringify(userDetailsResponse)));
}, []);

useEffect(() => {
Expand All @@ -85,15 +85,17 @@ export const UserDetail: React.FC<UserDetailProps> = (props) => {
const tenants: Tenant[] = getTenantsObjectsForIds(tenantListFromStore ?? [], data.tenantIds);
let matchingTenants: Tenant[] = [];

if (data.loginMethods[0].recipeId === "emailpassword") {
const PrimaryLoginMethod = data.loginMethods.filter((el) => el.recipeUserId === data.id)[0];

if (PrimaryLoginMethod.recipeId === "emailpassword") {
matchingTenants = tenants.filter((tenant) => tenant.emailPassword.enabled);
}

if (data.loginMethods[0].recipeId === "passwordless") {
if (PrimaryLoginMethod.recipeId === "passwordless") {
matchingTenants = tenants.filter((tenant) => tenant.passwordless.enabled);
}

if (data.loginMethods[0].recipeId === "thirdparty") {
if (PrimaryLoginMethod.recipeId === "thirdparty") {
matchingTenants = tenants.filter((tenant) => tenant.thirdParty.enabled);
}

Expand All @@ -105,7 +107,7 @@ export const UserDetail: React.FC<UserDetailProps> = (props) => {
setShowLoadingOverlay(false);
showModal(
getMissingTenantIdModalProps({
message: `User does not belong to a tenant that has the ${data.loginMethods[0].recipeId} recipe enabled`,
message: `User does not belong to a tenant that has the ${PrimaryLoginMethod.recipeId} recipe enabled`,
})
);

Expand All @@ -116,10 +118,10 @@ export const UserDetail: React.FC<UserDetailProps> = (props) => {

const userInfoResponse = await updateUserInformation({
userId,
recipeId: data.loginMethods[0].recipeId,
recipeUserId: data.loginMethods[0].recipeUserId,
email: data.emails[0],
phone: data.loginMethods[0].recipeId === "passwordless" ? data.phoneNumbers[0] : "",
recipeId: PrimaryLoginMethod.recipeId,
recipeUserId: PrimaryLoginMethod.recipeUserId,
email: PrimaryLoginMethod.email,
phone: PrimaryLoginMethod.recipeId === "passwordless" ? PrimaryLoginMethod.phoneNumber : "",
firstName: data.firstName,
lastName: data.lastName,
tenantId,
Expand Down
Loading

0 comments on commit 8e3b70e

Please sign in to comment.