Skip to content

Commit

Permalink
setup assistant UI
Browse files Browse the repository at this point in the history
  • Loading branch information
ghernandez345 committed Mar 20, 2024
1 parent 73e5f47 commit 7ae50b9
Show file tree
Hide file tree
Showing 22 changed files with 478 additions and 6 deletions.
14 changes: 12 additions & 2 deletions frontend/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ type CardColor = "white" | "gray" | "purple" | "yellow";

interface ICardProps {
children?: React.ReactNode;
/** The size of the border radius. Defaults to `small` */
/** The size of the border radius. Defaults to `small`. */
borderRadiusSize?: BorderRadiusSize;
/** Includes the card shadows. Defaults to `false` */
includeShadow?: boolean;
/** The color of the card. Defaults to `white` */
color?: CardColor;
className?: string;
/** Increases to 40px padding. Defaults to `false` */
/** The size of the padding around the content of the card. Defaults to `large`.
*
* These correspond to the padding sizes in the design system. Look at `padding.scss` for values */
paddingSize?: "small" | "medium" | "large" | "xlarge" | "xxlarge";
/** NOTE: DEPRICATED. Use `paddingSize` prop instead.
*
* Increases to 40px padding. Defaults to `false` */
largePadding?: boolean;
}

Expand All @@ -30,12 +36,16 @@ const Card = ({
color = "white",
className,
largePadding = false,
paddingSize = "large",
}: ICardProps) => {
const classNames = classnames(
baseClass,
`${baseClass}__${color}`,
`${baseClass}__radius-${borderRadiusSize}`,
{
// TODO: simplify this when we've replaced largePadding prop with paddingSize
[`${baseClass}__padding-${paddingSize}`]:
!largePadding && paddingSize !== undefined,
[`${baseClass}__shadow`]: includeShadow,
[`${baseClass}__large-padding`]: largePadding,
},
Expand Down
22 changes: 22 additions & 0 deletions frontend/components/Card/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,29 @@
box-shadow: $box-shadow;
}

&__padding-small {
padding: $pad-small;
}

&__padding-medium {
padding: $pad-medium;
}

&__padding-large {
padding: $pad-large;
}

&__padding-xlarge {
padding: $pad-xlarge;
}

&__padding-xxlarge {
padding: $pad-xxlarge;
}

// 40px padding
// TODO: remove when we've replaced all instances of largePadding with
// paddingSize prop
&__large-padding {
padding: $pad-xxlarge;
}
Expand Down
5 changes: 4 additions & 1 deletion frontend/components/FileUploader/FileUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ interface IFileUploaderProps {
* https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept
*/
accept?: string;
/** The text to display on the upload button */
buttonMessage?: string;
className?: string;
onFileUpload: (files: FileList | null) => void;
}
Expand All @@ -45,6 +47,7 @@ const FileUploader = ({
additionalInfo,
isLoading = false,
accept,
buttonMessage = "Upload",
className,
onFileUpload,
}: IFileUploaderProps) => {
Expand Down Expand Up @@ -73,7 +76,7 @@ const FileUploader = ({
variant="brand"
isLoading={isLoading}
>
<label htmlFor="upload-profile">Upload</label>
<label htmlFor="upload-profile">{buttonMessage}</label>
</Button>
<input
accept={accept}
Expand Down
4 changes: 2 additions & 2 deletions frontend/components/FileUploader/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@

&__upload-button {
margin-top: 8px;
// we handle the padding in the label so the entire button is clickable
padding: 0;
}

label {
height: 36px;
width: 78.2667px;
padding: $pad-small $pad-medium;
display: flex;
align-items: center;
justify-content: center;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ISideNavItem } from "pages/admin/components/SideNav/SideNav";

import EndUserAuthentication from "./cards/EndUserAuthentication/EndUserAuthentication";
import BootstrapPackage from "./cards/BootstrapPackage";
import SetupAssistant from "./cards/SetupAssistant";

interface ISetupExperienceCardProps {
currentTeamId?: number;
Expand All @@ -25,6 +26,12 @@ const SETUP_EXPERIENCE_NAV_ITEMS: ISideNavItem<
path: PATHS.CONTROLS_BOOTSTRAP_PACKAGE,
Card: BootstrapPackage,
},
{
title: "Setup assistant",
urlSection: "setup-assistant",
path: PATHS.CONTROLS_SETUP_ASSITANT,
Card: SetupAssistant,
},
];

export default SETUP_EXPERIENCE_NAV_ITEMS;
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
font-size: $x-small;

h2 {
font-size: $x-small; // needed to override global h2 style
margin: 0;
font-size: $small;
font-weight: normal;
}

&__preview-img {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React, { useState } from "react";

import SectionHeader from "components/SectionHeader";
import Spinner from "components/Spinner";

import SetupAssistantPreview from "./components/SetupAssistantPreview";
import SetupAssistantPackageUploader from "./components/SetupAssistantPackageUploader";
import SetuAssistantUploadedProfileView from "./components/SetupAssistantUploadedProfileView/SetupAssistantUploadedProfileView";

const baseClass = "setup-assistant";

interface ISetupAssistantProps {
currentTeamId: number;
}

const StartupAssistant = ({ currentTeamId }: ISetupAssistantProps) => {
const [showDeleteProfileModal, setShowDeleteProfileModal] = useState(false);

const isLoading = false;

const noPackageUploaded = true;

return (
<div className={baseClass}>
<SectionHeader title="Setup assistant" />
{isLoading ? (
<Spinner />
) : (
<div className={`${baseClass}__content`}>
{false ? (
<>
<SetupAssistantPackageUploader
currentTeamId={currentTeamId}
onUpload={() => 1}
/>
<div className={`${baseClass}__preview-container`}>
<SetupAssistantPreview />
</div>
</>
) : (
<>
<SetuAssistantUploadedProfileView
profileMetaData={1}
currentTeamId={currentTeamId}
onDelete={() => setShowDeleteProfileModal(true)}
/>
<div className={`${baseClass}__preview-container`}>
<SetupAssistantPreview />
</div>
</>
)}
</div>
)}
{/* {showDeleteProfileModal && (
<DeletePackageModal
onDelete={onDelete}
onCancel={() => setShowDeletePackageModal(false)}
/>
)} */}
</div>
);
};

export default StartupAssistant;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.setup-assistant {
&__content {
max-width: $break-xxl;
margin: 0 auto;
display: flex;
justify-content: space-between;
gap: $pad-xxlarge;
}

@media (max-width: $break-md) {
&__content {
flex-direction: column;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React, { useContext, useState } from "react";
import { AxiosResponse } from "axios";

import { IApiError } from "interfaces/errors";
import { NotificationContext } from "context/notification";
import mdmAPI from "services/entities/mdm";

import CustomLink from "components/CustomLink";
import FileUploader from "components/FileUploader";

import { UPLOAD_ERROR_MESSAGES, getErrorMessage } from "./helpers";

const baseClass = "setup-assistant-package-uploader";

interface ISetupAssistantPackageUploaderProps {
currentTeamId: number;
onUpload: () => void;
}

const SetupAssistantPackageUploader = ({
currentTeamId,
onUpload,
}: ISetupAssistantPackageUploaderProps) => {
const { renderFlash } = useContext(NotificationContext);
const [showLoading, setShowLoading] = useState(false);

const onUploadFile = async (files: FileList | null) => {
setShowLoading(true);

if (!files || files.length === 0) {
setShowLoading(false);
return;
}

const file = files[0];

// quick exit if the file type is incorrect
if (!file.name.includes(".pkg")) {
renderFlash("error", UPLOAD_ERROR_MESSAGES.wrongType.message);
setShowLoading(false);
return;
}

try {
await mdmAPI.uploadBootstrapPackage(file, currentTeamId);
renderFlash("success", "Successfully uploaded!");
onUpload();
} catch (e) {
const error = e as AxiosResponse<IApiError>;
const errMessage = getErrorMessage(error);
renderFlash("error", errMessage);
} finally {
setShowLoading(false);
}
};

return (
<div className={baseClass}>
<p>
Add an automatic enrollment profile to customize the macOS Setup
Assistant.
<CustomLink
url=" https://fleetdm.com/learn-more-about/setup-assistant"
text="Learn how"
newTab
/>
</p>
<FileUploader
message="Automatic enrollment profile (.json)"
graphicName="file-configuration-profile"
accept=".json"
buttonMessage="Add profile"
onFileUpload={onUploadFile}
isLoading={showLoading}
/>
</div>
);
};

export default SetupAssistantPackageUploader;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.setup-assistant-package-uploader {
> p {
font-size: $x-small;
margin: 0 0 $pad-large;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { AxiosResponse } from "axios";
import { IApiError } from "interfaces/errors";

export const UPLOAD_ERROR_MESSAGES = {
wrongType: {
condition: (reason: string) => reason.includes("invalid file type"),
message: "Couldn’t upload. The file should be a package (.pkg).",
},
unsigned: {
condition: (reason: string) => reason.includes("file is not"),
message:
"Couldn’t upload. The package must be signed. Click “Learn more” below to learn how to sign.",
},
default: {
condition: () => false,
message: "Couldn’t upload. Please try again.",
},
};

export const getErrorMessage = (err: AxiosResponse<IApiError>) => {
const apiReason = err.data.errors[0].reason;

const error = Object.values(UPLOAD_ERROR_MESSAGES).find((errType) =>
errType.condition(apiReason)
);

if (!error) {
return UPLOAD_ERROR_MESSAGES.default.message;
}

return error.message;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./SetupAssistantPackageUploader";
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

import Card from "components/Card";

import OsSetupPreview from "../../../../../../../../assets/images/os-setup-preview.gif";

const baseClass = "setup-assistant-preview";

const SetupAssistantPreview = () => {
return (
<Card
color="gray"
borderRadiusSize="medium"
paddingSize="xxlarge"
className={baseClass}
>
<h2>End user experience</h2>
<p>
After the end user continues past the <b>Remote Management</b> screen,
macOS Setup Assistant displays several screens by default.
</p>
<p>
By adding an automatic enrollment profile you can customize which
screens are displayed and more.
</p>
<img
className={`${baseClass}__preview-img`}
src={OsSetupPreview}
alt="OS setup preview"
/>
</Card>
);
};

export default SetupAssistantPreview;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.setup-assistant-preview {
font-size: $x-small;

h2 {
margin: 0;
font-size: $small;
font-weight: normal;
}

&__preview-img {
margin-top: $pad-xxlarge;
width: 100%;
display: block;
margin: 40px auto 0;
}
}
Loading

0 comments on commit 7ae50b9

Please sign in to comment.