Skip to content

Commit

Permalink
Style share modal (#775)
Browse files Browse the repository at this point in the history
* Style share modal:

* Share modal styling
* Org card gear cog btn bug fix
* Toasts desktop position
* Org roles warmimg styling
* Opp card overflow bug fix
* Badges overflow
* Hide Gleac on mobile

* Format & Linting

* Org edt/create Sso ids

* Add fields for Sso ids to org create/edit screens
* Sso Inbound/Outbound counts to Org Dashboard

* Org card dropdown mobile btn fix
Matthew-Baird authored May 3, 2024
1 parent f2fd0e3 commit 2353ac2
Showing 13 changed files with 221 additions and 46 deletions.
4 changes: 4 additions & 0 deletions src/web/src/api/models/organisation.ts
Original file line number Diff line number Diff line change
@@ -27,6 +27,8 @@ export interface OrganizationRequestBase {
registrationDocumentsDelete: string[] | null;
educationProviderDocumentsDelete: string[] | null;
businessDocumentsDelete: string[] | null;
ssoClientIdInbound: string | null;
ssoClientIdOutbound: string | null;
}

export interface OrganizationProviderType {
@@ -63,6 +65,8 @@ export interface Organization {
documents: OrganizationDocument[] | null;
providerTypes: OrganizationProviderType[] | null;
administrators: UserInfo[] | null;
ssoClientIdInbound: string | null;
ssoClientIdOutbound: string | null;
}

export interface UserInfo {
12 changes: 12 additions & 0 deletions src/web/src/api/models/organizationDashboard.ts
Original file line number Diff line number Diff line change
@@ -140,3 +140,15 @@ export interface OrganizationSearchResultsYouth {
totalCount: number;
dateStamp: string;
}

export interface OrganizationSearchSso {
outbound: SsoInfo;
inbound: SsoInfo;
dateStamp: string;
}

export interface SsoInfo {
enabled: boolean;
clientId: string;
loginCount: number;
}
13 changes: 13 additions & 0 deletions src/web/src/api/services/organizationDashboard.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import type {
OrganizationSearchResultsOpportunity,
OrganizationSearchResultsSummary,
OrganizationSearchResultsYouth,
OrganizationSearchSso,
} from "../models/organizationDashboard";

export const searchOrganizationEngagement = async (
@@ -164,3 +165,15 @@ export const searchOrganizationYouth = async (
);
return data;
};

export const searchOrganizationSso = async (
filter: OrganizationSearchFilterBase,
context?: GetServerSidePropsContext,
): Promise<OrganizationSearchSso> => {
const instance = context ? ApiServer(context) : await ApiClient;
const { data } = await instance.post<OrganizationSearchSso>(
"/organization/search/analytics/sso",
filter,
);
return data;
};
4 changes: 2 additions & 2 deletions src/web/src/components/Opportunity/OpportunityPublicSmall.tsx
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ const OpportunityPublicSmallComponent: React.FC<InputProps> = ({ data }) => {
return (
<Link
href={`/opportunities/${data.id}`}
className="relative flex aspect-square h-[19rem] w-max flex-col gap-1 rounded-lg bg-white p-4 shadow-sm md:ml-0"
className="relative flex aspect-square h-[19rem] w-max flex-col gap-1 overflow-hidden rounded-lg bg-white p-4 shadow-sm md:ml-0"
>
<div className="flex flex-row">
<div className="flex flex-row">
@@ -61,7 +61,7 @@ const OpportunityPublicSmallComponent: React.FC<InputProps> = ({ data }) => {
)}

{/* BADGES */}
<div className="flex flex-row flex-wrap gap-2 whitespace-nowrap pt-2 text-green-dark md:flex-nowrap">
<div className="flex flex-row flex-wrap gap-2 whitespace-normal pt-2 text-green-dark md:flex-nowrap md:whitespace-nowrap">
<div className="badge bg-green-light text-green">
<Image
src={iconClock}
56 changes: 30 additions & 26 deletions src/web/src/components/Opportunity/Share.tsx
Original file line number Diff line number Diff line change
@@ -130,27 +130,27 @@ const SharePopup: React.FC<SharePopupProps> = ({ opportunity, onClose }) => {
{/* MAIN CONTENT */}
{!linkInfoIsLoading && (
<div className="flex flex-col items-center justify-center gap-4 p-8">
<div className="-mt-16 flex h-12 w-12 items-center justify-center rounded-full border-green-dark bg-white shadow-lg">
<IoShareSocialOutline className="h-7 w-7" />
<div className="-mt-16 flex h-12 w-12 items-center justify-center rounded-full border-green-dark bg-orange shadow-lg">
<IoShareSocialOutline className="mr-px h-7 w-7 text-white" />
</div>

<h3>Share this opportunity!</h3>

{/* OPPORTUNITY DETAILS (smaller) */}
<div className="flex w-full flex-col rounded-lg border-2 border-dotted border-gray p-4">
<div className="flex w-full flex-col rounded-lg p-4 shadow-custom">
<div className="flex gap-4">
<div className="">
<div className="mb-1 flex items-center">
<AvatarImage
icon={opportunity?.organizationLogoURL ?? null}
alt={`${opportunity?.organizationName} Logo`}
size={60}
/>
</div>
<div className="flex max-w-[200px] flex-col gap-1 sm:max-w-[480px] md:max-w-[420px]">
<h4 className="overflow-hidden text-ellipsis whitespace-nowrap text-sm font-semibold leading-7 text-black md:text-xl md:leading-8">
<div className="flex flex-col gap-1 md:max-w-[420px]">
<h4 className="line-clamp-2 max-w-[170px] overflow-hidden text-ellipsis text-[18px] font-semibold leading-tight md:max-w-[420px]">
{opportunity?.title}
</h4>
<h6 className=" overflow-hidden text-ellipsis whitespace-nowrap text-xs text-gray-dark">
<h6 className="line-clamp-2 max-w-[180px] overflow-hidden text-ellipsis text-xs font-medium text-gray-dark md:max-w-[420px]">
By {opportunity?.organizationName}
</h6>
</div>
@@ -191,52 +191,56 @@ const SharePopup: React.FC<SharePopupProps> = ({ opportunity, onClose }) => {
</div>

{/* BUTTONS */}
<div className="grid w-full grid-cols-1 gap-4 md:grid-cols-2">
<div className="mb-2 mt-10 grid w-full grid-cols-1 gap-4 md:mb-6 md:grid-cols-2">
<button
onClick={onClick_CopyToClipboard}
className="flex w-full items-center gap-2 rounded-xl border-[1px] border-gray px-4 py-3 text-sm font-semibold text-black hover:bg-gray-light md:text-lg"
className="btn btn-outline btn-primary"
>
<IoCopy className="mr-2 h-6 w-6" />
Copy Link
</button>
<button
onClick={onClick_GenerateQRCode}
className="flex w-full items-center gap-2 rounded-xl border-[1px] border-gray px-4 py-3 text-sm font-semibold text-black hover:bg-gray-light md:text-lg"
className="btn btn-outline btn-primary"
>
<IoQrCode className="mr-2 h-6 w-6" />
Generate QR Code
</button>

<button
onClick={onClick_MoreOptions}
className="flex w-full items-center gap-2 rounded-xl border-[1px] border-gray px-4 py-3 text-sm font-semibold text-black hover:bg-gray-light md:text-lg"
className="btn btn-outline btn-primary md:hidden"
>
<IoEllipsisHorizontalOutline className="mr-2 h-6 w-6 text-black" />
<IoEllipsisHorizontalOutline className="mr-2 h-6 w-6" />
More options
</button>
</div>

{/* QR CODE */}
{showQRCode && qrCodeImageData && (
<>
<h5>Scan the QR Code with your device&apos;s camera</h5>
<span className="mt-4">
<h5 className="text-center">
Scan the QR Code with your device&apos;s camera
</h5>
<Image
src={qrCodeImageData}
alt="QR Code"
width={200}
height={200}
style={{ width: 200, height: 200 }}
width={320}
height={320}
/>
</>
</span>
)}

<button
type="button"
className="btn mt-10 rounded-full border-purple bg-white normal-case text-purple md:w-[150px]"
onClick={onClose}
ref={cancelButtonRef}
>
Close
</button>
{showQRCode && (
<button
type="button"
className="btn btn-primary w-full md:w-[168px]"
onClick={onClose}
ref={cancelButtonRef}
>
Close
</button>
)}
</div>
)}
</div>
Original file line number Diff line number Diff line change
@@ -147,7 +147,7 @@ export const OrganisationCardComponent: React.FC<{
</p>

<div className="mt-2 flex flex-row">
<div className="flex flex-grow flex-row items-center">
<div className="z-30 flex flex-grow flex-row items-center">
{item.status == "Active" && (
<>
<span className="mr-2 h-2 w-2 rounded-full bg-success"></span>
@@ -168,12 +168,12 @@ export const OrganisationCardComponent: React.FC<{
)}
</div>

<div className="dropdown dropdown-left -mr-3 w-10 md:-mr-4">
<div className="dropdown dropdown-left -mr-0 w-10 md:-mr-4">
<button
className="bg-theme xl:hover:bg-theme flex flex-row items-center justify-center whitespace-nowrap rounded-full p-1 text-xs text-white brightness-105 disabled:cursor-not-allowed disabled:bg-gray-dark xl:hover:brightness-110"
className="bg-theme xl:hover:bg-theme !z-50 rounded-full p-1 text-xs text-white brightness-105 disabled:cursor-not-allowed disabled:bg-gray-dark xl:hover:brightness-110"
disabled={item?.status == "Deleted"}
>
<IoIosSettings className="h-7 w-7 md:h-5 md:w-5" />
<IoIosSettings className="!z-50 h-7 w-7 md:h-5 md:w-5" />
</button>

<ul className="menu dropdown-content z-50 w-52 rounded-box bg-base-100 p-2 shadow">
52 changes: 52 additions & 0 deletions src/web/src/components/Organisation/Upsert/OrgAdminsEdit.tsx
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@ export interface InputProps {
onCancel?: (fieldValues: FieldValues) => void;
cancelButtonText?: string;
submitButtonText?: string;
isAdmin?: boolean;
}

export const OrgAdminsEdit: React.FC<InputProps> = ({
@@ -20,11 +21,14 @@ export const OrgAdminsEdit: React.FC<InputProps> = ({
onCancel,
cancelButtonText = "Cancel",
submitButtonText = "Submit",
isAdmin = false,
}) => {
const schema = zod
.object({
addCurrentUserAsAdmin: zod.boolean().optional(),
adminEmails: zod.array(zod.string().email()).optional(),
ssoClientIdInbound: zod.string().optional(),
ssoClientIdOutbound: zod.string().optional(),
})
.nonstrict()

@@ -141,6 +145,54 @@ export const OrgAdminsEdit: React.FC<InputProps> = ({
)}
</div>

{isAdmin && (
<>
<div className="form-control">
<label className="label font-bold">
<span className="label-text">SSO Client Id Inbound</span>
</label>
<p className="-mt-1 mb-2 ml-1 text-sm text-gray-dark">
Your organisation&apos;s SSO client inbound id
</p>
<input
type="text"
className="input input-bordered rounded-md border-gray focus:border-gray focus:outline-none"
{...register("ssoClientIdInbound")}
data-autocomplete="sso-client-id-inbound"
/>
{formState.errors.ssoClientIdInbound && (
<label className="label font-bold">
<span className="label-text-alt italic text-red-500">
{`${formState.errors.ssoClientIdInbound.message}`}
</span>
</label>
)}
</div>

<div className="form-control">
<label className="label font-bold">
<span className="label-text">SSO Client Id Outbound</span>
</label>
<p className="-mt-1 mb-2 ml-1 text-sm text-gray-dark">
Your organisation&apos;s SSO client outbound id
</p>
<input
type="text"
className="input input-bordered rounded-md border-gray focus:border-gray focus:outline-none"
{...register("ssoClientIdOutbound")}
data-autocomplete="sso-client-id-outbound"
/>
{formState.errors.ssoClientIdOutbound && (
<label className="label font-bold">
<span className="label-text-alt italic text-red-500">
{`${formState.errors.ssoClientIdOutbound.message}`}
</span>
</label>
)}
</div>
</>
)}

{/* BUTTONS */}
<div className="mt-4 flex flex-row items-center justify-end gap-4">
{onCancel && (
2 changes: 1 addition & 1 deletion src/web/src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -104,7 +104,7 @@ const MyApp = ({
containerId="toastContainer"
bodyClassName={"test1"}
toastClassName={"test2"}
className="mt-16 w-full md:mt-10 md:w-[340px]"
className="mt-2 w-full md:mt-0 md:w-[340px]"
theme="colored"
/>
<GoogleAnalytics />
7 changes: 3 additions & 4 deletions src/web/src/pages/opportunities/[opportunityId]/index.tsx
Original file line number Diff line number Diff line change
@@ -360,7 +360,7 @@ const OpportunityDetails: NextPageWithLayout<{
</Head>
<PageBackground />

<div className="container z-10 mt-16 max-w-7xl px-2 py-4 md:mt-20">
<div className="container z-10 mt-16 max-w-7xl overflow-hidden px-2 py-4 md:mt-20">
<>
{/* MAIN CONTENT */}
<div className="flex flex-col gap-2 py-6 sm:flex-row">
@@ -686,7 +686,7 @@ const OpportunityDetails: NextPageWithLayout<{
onRequestClose={() => {
setShareOpportunityDialogVisible(false);
}}
className={`fixed bottom-0 left-0 right-0 top-0 flex-grow overflow-hidden bg-white animate-in fade-in md:m-auto md:max-h-[650px] md:w-[600px] md:rounded-3xl`}
className={`fixed bottom-0 left-0 right-0 top-0 w-full flex-grow overflow-hidden bg-white animate-in fade-in md:m-auto md:max-h-[500px] md:w-[600px] md:rounded-3xl`}
portalClassName={"fixed z-40"}
overlayClassName="fixed inset-0 bg-overlay"
>
@@ -908,14 +908,13 @@ const OpportunityDetails: NextPageWithLayout<{
priority={true}
style={{ width: "20px", height: "20px" }}
/>

<span className="ml-1">Skills you will learn</span>
</div>
<div className="my-2 flex flex-wrap gap-1">
{opportunityInfo.skills?.map((item) => (
<div
key={item.id}
className="min-h-6 badge h-full bg-green py-1 text-white"
className="badge bg-green px-2 py-1 text-white"
>
{item.name}
</div>
12 changes: 8 additions & 4 deletions src/web/src/pages/organisations/[id]/edit.tsx
Original file line number Diff line number Diff line change
@@ -39,10 +39,11 @@ import { Unauthorized } from "~/components/Status/Unauthorized";
import {
GA_ACTION_ORGANISATION_UPATE,
GA_CATEGORY_ORGANISATION,
ROLE_ADMIN,
} from "~/lib/constants";
import { config } from "~/lib/react-query-config";
import { getCountries } from "~/api/services/lookups";
import { IoMdArrowRoundBack, IoMdWarning } from "react-icons/io";
import { IoIosWarning, IoMdArrowRoundBack } from "react-icons/io";
import { trackGAEvent } from "~/lib/google-analytics";
import { getSafeUrl, getThemeFromRole } from "~/lib/utils";
import axios from "axios";
@@ -132,7 +133,7 @@ const OrganisationUpdate: NextPageWithLayout<{
const setCurrentOrganisationInactiveAtom = useSetAtom(
currentOrganisationInactiveAtom,
);

const isAdmin = user?.roles?.includes(ROLE_ADMIN);
const isUserAdminOfCurrentOrg =
userProfile?.adminsOf?.find((x) => x.id == id) != null;

@@ -173,6 +174,8 @@ const OrganisationUpdate: NextPageWithLayout<{
registrationDocumentsDelete: [],
educationProviderDocumentsDelete: [],
businessDocumentsDelete: [],
ssoClientIdInbound: organisation?.ssoClientIdInbound ?? "",
ssoClientIdOutbound: organisation?.ssoClientIdOutbound ?? "",
});

const onSubmit = useCallback(
@@ -395,8 +398,8 @@ const OrganisationUpdate: NextPageWithLayout<{
</h5>
</div>

<p className="my-2 flex flex-row border-2 border-dotted border-warning p-2 text-warning">
<IoMdWarning className="mr-2 inline-block h-12 w-12" />
<p className="my-2 flex flex-row items-center gap-4 rounded-xl bg-green px-4 py-2 text-sm text-white">
<IoIosWarning className="inline-block h-6 w-6" />
Kindly note that expanding the roles your organization plays
in Yoma will necessitate re-verification of your organization.
<br /> During this process, functionalities such as creating
@@ -421,6 +424,7 @@ const OrganisationUpdate: NextPageWithLayout<{
<OrgAdminsEdit
organisation={OrganizationRequestBase}
onSubmit={(data) => onSubmitStep(4, data)}
isAdmin={isAdmin}
/>
</>
)}
88 changes: 84 additions & 4 deletions src/web/src/pages/organisations/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ import {
searchOrganizationEngagement,
searchOrganizationOpportunities,
searchOrganizationYouth,
searchOrganizationSso,
} from "~/api/services/organizationDashboard";
import type { GetServerSidePropsContext } from "next";
import type {
@@ -51,6 +52,7 @@ import {
DATETIME_FORMAT_HUMAN,
PAGE_SIZE,
PAGE_SIZE_MINIMUM,
ROLE_ADMIN,
} from "~/lib/constants";
import NoRowsMessage from "~/components/NoRowsMessage";
import { PaginationButtons } from "~/components/PaginationButtons";
@@ -59,6 +61,7 @@ import type {
OrganizationSearchResultsOpportunity,
OrganizationSearchResultsSummary,
OrganizationSearchResultsYouth,
OrganizationSearchSso,
} from "~/api/models/organizationDashboard";
import { LoadingSkeleton } from "~/components/Status/LoadingSkeleton";
import moment from "moment";
@@ -76,7 +79,7 @@ import { AvatarImage } from "~/components/AvatarImage";
import DashboardCarousel from "~/components/Organisation/Dashboard/DashboardCarousel";
import { WorldMapChart } from "~/components/Organisation/Dashboard/WorldMapChart";
import type { Organization } from "~/api/models/organisation";

import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
interface OrganizationSearchFilterSummaryViewModel {
organization: string;
opportunities: string[] | null;
@@ -177,6 +180,7 @@ const OrganisationDashboard: NextPageWithLayout<{
useState(0);
const [expiredOpportunitiesCount, setExpiredOpportunitiesCount] = useState(0);
const queryClient = useQueryClient();
const isAdmin = user?.roles?.includes(ROLE_ADMIN);

// 👇 use prefetched queries from server
const { data: lookups_categories } = useQuery<OpportunityCategory[]>({
@@ -326,6 +330,34 @@ const OrganisationDashboard: NextPageWithLayout<{
}),
});

// QUERY: SSO
const { data: ssoData, isLoading: ssoDataIsLoading } =
useQuery<OrganizationSearchSso>({
queryKey: ["searchOrganizationSso", id, startDate, endDate],
queryFn: () =>
searchOrganizationSso({
organization: id,
categories:
categories != undefined
? categories
?.toString()
.split(",")
.map((x) => {
const item = lookups_categories?.find((y) => y.name === x);
return item ? item?.id : "";
})
.filter((x) => x != "")
: null,
opportunities: opportunities
? opportunities?.toString().split(",")
: null,
startDate: startDate ? startDate.toString() : "",
endDate: endDate ? endDate.toString() : "",
pageNumber: null,
pageSize: null,
}),
});

// search filter state
const [searchFilter, setSearchFilter] =
useState<OrganizationSearchFilterSummaryViewModel>({
@@ -685,11 +717,13 @@ const OrganisationDashboard: NextPageWithLayout<{
{/* WELCOME MSG */}
<div className="text-2xl font-semibold text-white md:text-3xl">
<span>
{timeOfDayEmoji} Good {timeOfDay}
<span className="">{user?.name}</span>, here is your report
{timeOfDayEmoji} Good {timeOfDay}&nbsp;
<span className="">{user?.name}</span>, here is your reports
for&nbsp;
<span className="hidden md:block" />
<span className="font-light">{organisation?.name}</span>
<span className="line-clamp-2 font-semibold">
{organisation?.name}
</span>
</span>
</div>

@@ -1318,6 +1352,52 @@ const OrganisationDashboard: NextPageWithLayout<{
</div>
)}
</div>

{/* DIVIDER */}
{isAdmin && ssoData && (
<div className="border-px my-8 border-t border-gray" />
)}

{/* SSO */}
{isAdmin && (
<div className="my-8 flex flex-col gap-4">
<div className="text-2xl font-semibold">Single Sign On</div>
{ssoDataIsLoading && <LoadingSkeleton />}
{ssoData && (
<div className="grid grid-rows-2 gap-4 md:grid-cols-2">
<div className="flex flex-col gap-2 rounded-lg bg-white p-6 shadow">
<div className="mb-2 flex items-center gap-2 text-lg font-semibold">
<div>Outbound</div>{" "}
<IoIosArrowForward className="rounded-lg bg-green-light p-px pl-[2px] text-2xl text-green" />
</div>
{ssoData?.inbound?.enabled ? (
<>
{" "}
<div>Client Id: {ssoData?.inbound?.clientId}</div>
<div>Login count: {ssoData?.inbound?.loginCount}</div>
</>
) : (
<div>Disabled</div>
)}
</div>
<div className="flex flex-col gap-2 rounded-lg bg-white p-6 shadow">
<div className="mb-2 flex items-center gap-2 text-lg font-semibold">
<div>Inbound</div>{" "}
<IoIosArrowBack className="rounded-lg bg-green-light p-px pr-[2px] text-2xl text-green" />
</div>
{ssoData?.inbound?.enabled ? (
<>
<div>Client Id: {ssoData?.outbound?.clientId}</div>
<div>Login count: {ssoData?.outbound?.loginCount}</div>
</>
) : (
<div>Disabled</div>
)}
</div>
</div>
)}
</div>
)}
</div>
</div>
</>
4 changes: 4 additions & 0 deletions src/web/src/pages/organisations/register/index.tsx
Original file line number Diff line number Diff line change
@@ -95,6 +95,7 @@ const OrganisationCreate: NextPageWithLayout<{
const [step, setStep] = useState(1);
const setUserProfile = useSetAtom(userProfileAtom);
const { data: session, update } = useSession();
const isAdmin = session?.user?.roles.includes(ROLE_ADMIN);

const [OrganizationRequestBase, setOrganizationRequestBase] =
useState<OrganizationRequestBase>({
@@ -124,6 +125,8 @@ const OrganisationCreate: NextPageWithLayout<{
businessDocumentsDelete: [],
educationProviderDocumentsDelete: [],
registrationDocumentsDelete: [],
ssoClientIdInbound: "",
ssoClientIdOutbound: "",
});

const onSubmit = useCallback(
@@ -295,6 +298,7 @@ const OrganisationCreate: NextPageWithLayout<{
onSubmit={(data) => onSubmitStep(4, data)}
cancelButtonText="Back"
submitButtonText="Submit for approval"
isAdmin={isAdmin}
/>
</>
)}
5 changes: 4 additions & 1 deletion src/web/src/styles/globals.scss
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@
}

.badge {
@apply h-6 whitespace-nowrap rounded-md border-none text-xs font-bold;
@apply h-6 overflow-hidden whitespace-nowrap rounded-md border-none text-xs font-bold;
}

.btn {
@@ -111,6 +111,9 @@ body {
.widget {
visibility: hidden !important;
}
#hubspot-messages-iframe-container {
visibility: hidden !important;
}
}

// NProgress

0 comments on commit 2353ac2

Please sign in to comment.