├── .editorconfig ├── .github ├── stale.yml └── workflows │ ├── add-to-project.yml │ ├── e2e.feature.yml │ ├── e2e.groups.yml │ ├── e2e.project-access.yml │ ├── e2e.segments.yml │ ├── node.js.yml │ ├── release.yml │ └── release_changelog.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── integration │ ├── feature │ │ └── feature.spec.ts │ ├── groups │ │ └── groups.spec.ts │ ├── projects │ │ └── access │ │ │ └── project-access.spec.ts │ └── segments │ │ └── segments.spec.ts ├── plugins │ └── index.ts └── support │ ├── commands.ts │ └── index.ts ├── index.html ├── index.js ├── package.json ├── public ├── cs_CZ.png ├── da-DK.png ├── datadog.svg ├── de_DE.png ├── en-GB.png ├── en-IN.png ├── en-US.png ├── favicon.ico ├── favicon_old.ico ├── flags-normal │ ├── ad.png │ ├── ae.png │ ├── af.png │ ├── ag.png │ ├── al.png │ ├── am.png │ ├── ao.png │ ├── ar.png │ ├── at.png │ ├── au.png │ ├── az.png │ ├── ba.png │ ├── bb.png │ ├── bd.png │ ├── be.png │ ├── bf.png │ ├── bg.png │ ├── bh.png │ ├── bi.png │ ├── bj.png │ ├── bn.png │ ├── bo.png │ ├── br.png │ ├── bs.png │ ├── bt.png │ ├── bw.png │ ├── by.png │ ├── bz.png │ ├── ca.png │ ├── cd.png │ ├── cf.png │ ├── cg.png │ ├── ch.png │ ├── ci.png │ ├── cl.png │ ├── cm.png │ ├── cn.png │ ├── co.png │ ├── cr.png │ ├── cu.png │ ├── cv.png │ ├── cy.png │ ├── cz.png │ ├── de.png │ ├── dj.png │ ├── dk.png │ ├── dm.png │ ├── do.png │ ├── dz.png │ ├── ec.png │ ├── ee.png │ ├── eg.png │ ├── eh.png │ ├── er.png │ ├── es.png │ ├── et.png │ ├── fi.png │ ├── fj.png │ ├── fm.png │ ├── fr.png │ ├── ga.png │ ├── gb.png │ ├── gd.png │ ├── ge.png │ ├── gh.png │ ├── gm.png │ ├── gn.png │ ├── gq.png │ ├── gr.png │ ├── gt.png │ ├── gw.png │ ├── gy.png │ ├── hn.png │ ├── hr.png │ ├── ht.png │ ├── hu.png │ ├── id.png │ ├── ie.png │ ├── il.png │ ├── in.png │ ├── iq.png │ ├── ir.png │ ├── is.png │ ├── it.png │ ├── jm.png │ ├── jo.png │ ├── jp.png │ ├── ke.png │ ├── kg.png │ ├── kh.png │ ├── ki.png │ ├── km.png │ ├── kn.png │ ├── kp.png │ ├── kr.png │ ├── ks.png │ ├── kw.png │ ├── kz.png │ ├── la.png │ ├── lb.png │ ├── lc.png │ ├── li.png │ ├── lk.png │ ├── lr.png │ ├── ls.png │ ├── lt.png │ ├── lu.png │ ├── lv.png │ ├── ly.png │ ├── ma.png │ ├── mc.png │ ├── md.png │ ├── me.png │ ├── mg.png │ ├── mh.png │ ├── mk.png │ ├── ml.png │ ├── mm.png │ ├── mn.png │ ├── mr.png │ ├── mt.png │ ├── mu.png │ ├── mv.png │ ├── mw.png │ ├── mx.png │ ├── my.png │ ├── mz.png │ ├── na.png │ ├── ne.png │ ├── ng.png │ ├── ni.png │ ├── nl.png │ ├── no.png │ ├── np.png │ ├── nr.png │ ├── nz.png │ ├── om.png │ ├── pa.png │ ├── pe.png │ ├── pg.png │ ├── ph.png │ ├── pk.png │ ├── pl.png │ ├── pt.png │ ├── pw.png │ ├── py.png │ ├── qa.png │ ├── ro.png │ ├── rs.png │ ├── ru.png │ ├── rw.png │ ├── sa.png │ ├── sb.png │ ├── sc.png │ ├── sd.png │ ├── se.png │ ├── sg.png │ ├── si.png │ ├── sk.png │ ├── sl.png │ ├── sm.png │ ├── sn.png │ ├── so.png │ ├── sr.png │ ├── st.png │ ├── sv.png │ ├── sy.png │ ├── sz.png │ ├── td.png │ ├── tg.png │ ├── th.png │ ├── tj.png │ ├── tl.png │ ├── tm.png │ ├── tn.png │ ├── to.png │ ├── tr.png │ ├── tt.png │ ├── tv.png │ ├── tw.png │ ├── tz.png │ ├── ua.png │ ├── ug.png │ ├── us.png │ ├── uy.png │ ├── uz.png │ ├── va.png │ ├── vc.png │ ├── ve.png │ ├── vn.png │ ├── vu.png │ ├── ws.png │ ├── ye.png │ ├── za.png │ ├── zm.png │ └── zw.png ├── fr-FR.png ├── jira.svg ├── logo-filled.png ├── logo-inverted.png ├── logo.png ├── logo_old.png ├── nb-NO.png ├── pt_BR.png ├── slack.svg ├── sv-SE.png ├── switches.svg ├── teams.svg ├── unknown-locale.png └── webhooks.svg ├── renovate.json ├── scripts └── generate-openapi.sh ├── src ├── __mocks__ │ ├── fileMock.js │ └── svgMock.js ├── assets │ ├── fonts │ │ ├── roboto300.ttf │ │ ├── roboto400.ttf │ │ ├── roboto500.ttf │ │ ├── roboto700.ttf │ │ ├── senBold.ttf │ │ ├── senExtraBold.ttf │ │ └── senRegular.ttf │ ├── icons │ │ ├── 24_Negator off.svg │ │ ├── 24_Negator.svg │ │ ├── 24_Text format off.svg │ │ ├── 24_Text format.svg │ │ ├── Icecream.svg │ │ ├── addfiles.svg │ │ ├── datadog.svg │ │ ├── dots.svg │ │ ├── email.svg │ │ ├── google.svg │ │ ├── gradual.svg │ │ ├── isenabled-false.svg │ │ ├── isenabled-true.svg │ │ ├── jira.svg │ │ ├── logoBg.svg │ │ ├── logoInverted.svg │ │ ├── logoPlain.svg │ │ ├── logoWhiteBg.svg │ │ ├── projectIcon.svg │ │ ├── reorder.svg │ │ ├── rollout.svg │ │ ├── slack.svg │ │ ├── star.svg │ │ ├── switches.svg │ │ ├── teams.svg │ │ ├── toggleLeft.svg │ │ ├── toggleRight.svg │ │ ├── unknownUser.png │ │ └── webhooks.svg │ └── img │ │ ├── logo.png │ │ ├── logo.svg │ │ ├── logoDark.svg │ │ ├── logoDarkWithText.svg │ │ ├── logoWhiteTransparentHorizontal.svg │ │ ├── logoWithName.svg │ │ ├── logoWithWhiteText.svg │ │ ├── mobileGuidanceBg.svg │ │ ├── texture.png │ │ └── unleashLogoIconDarkAlpha.gif ├── component │ ├── App.styles.ts │ ├── App.tsx │ ├── addons │ │ ├── AddonForm │ │ │ ├── AddonForm.styles.tsx │ │ │ ├── AddonForm.tsx │ │ │ ├── AddonMultiSelector │ │ │ │ ├── AddonMultiSelector.test.tsx │ │ │ │ └── AddonMultiSelector.tsx │ │ │ └── AddonParameters │ │ │ │ ├── AddonParameter │ │ │ │ └── AddonParameter.tsx │ │ │ │ └── AddonParameters.tsx │ │ ├── AddonList │ │ │ ├── AddonIcon │ │ │ │ └── AddonIcon.tsx │ │ │ ├── AddonList.tsx │ │ │ ├── AvailableAddons │ │ │ │ ├── AvailableAddons.tsx │ │ │ │ └── ConfigureAddonButton │ │ │ │ │ └── ConfigureAddonButton.tsx │ │ │ └── ConfiguredAddons │ │ │ │ ├── ConfiguredAddons.tsx │ │ │ │ └── ConfiguredAddonsActionCell │ │ │ │ └── ConfiguredAddonsActionsCell.tsx │ │ ├── CreateAddon │ │ │ └── CreateAddon.tsx │ │ └── EditAddon │ │ │ └── EditAddon.tsx │ ├── admin │ │ ├── api │ │ │ └── index.tsx │ │ ├── apiToken │ │ │ ├── ApiTokenDocs │ │ │ │ └── ApiTokenDocs.tsx │ │ │ ├── ApiTokenForm │ │ │ │ ├── ApiTokenForm.styles.ts │ │ │ │ ├── ApiTokenForm.tsx │ │ │ │ ├── SelectProjectInput │ │ │ │ │ ├── SelectAllButton │ │ │ │ │ │ ├── SelectAllButton.styles.ts │ │ │ │ │ │ └── SelectAllButton.tsx │ │ │ │ │ ├── SelectProjectInput.styles.ts │ │ │ │ │ ├── SelectProjectInput.test.tsx │ │ │ │ │ └── SelectProjectInput.tsx │ │ │ │ └── useApiTokenForm.ts │ │ │ ├── ApiTokenPage │ │ │ │ └── ApiTokenPage.tsx │ │ │ ├── ApiTokenTable │ │ │ │ └── ApiTokenTable.tsx │ │ │ ├── ConfirmToken │ │ │ │ ├── ConfirmToken.tsx │ │ │ │ └── UserToken │ │ │ │ │ └── UserToken.tsx │ │ │ ├── CopyApiTokenButton │ │ │ │ └── CopyApiTokenButton.tsx │ │ │ ├── CreateApiToken │ │ │ │ └── CreateApiToken.tsx │ │ │ ├── CreateApiTokenButton │ │ │ │ └── CreateApiTokenButton.tsx │ │ │ ├── ProjectsList │ │ │ │ ├── ProjectsList.test.tsx │ │ │ │ └── ProjectsList.tsx │ │ │ └── RemoveApiTokenButton │ │ │ │ └── RemoveApiTokenButton.tsx │ │ ├── auth │ │ │ ├── AuthSettings.tsx │ │ │ ├── AutoCreateForm │ │ │ │ └── AutoCreateForm.tsx │ │ │ ├── GoogleAuth │ │ │ │ └── GoogleAuth.tsx │ │ │ ├── OidcAuth │ │ │ │ └── OidcAuth.tsx │ │ │ ├── PasswordAuth │ │ │ │ └── PasswordAuth.tsx │ │ │ └── SamlAuth │ │ │ │ └── SamlAuth.tsx │ │ ├── billing │ │ │ ├── Billing.tsx │ │ │ ├── BillingDashboard │ │ │ │ ├── BillingDashboard.tsx │ │ │ │ ├── BillingInformation │ │ │ │ │ ├── BillingInformation.tsx │ │ │ │ │ └── BillingInformationButton │ │ │ │ │ │ └── BillingInformationButton.tsx │ │ │ │ └── BillingPlan │ │ │ │ │ ├── BillingPlan.tsx │ │ │ │ │ └── GridColLink │ │ │ │ │ └── GridColLink.tsx │ │ │ ├── BillingHistory │ │ │ │ └── BillingHistory.tsx │ │ │ ├── FlaggedBillingRedirect │ │ │ │ └── FlaggedBillingRedirect.tsx │ │ │ └── flags.ts │ │ ├── cors │ │ │ ├── CorsForm.test.tsx │ │ │ ├── CorsForm.tsx │ │ │ ├── CorsHelpAlert.tsx │ │ │ ├── CorsTokenAlert.tsx │ │ │ └── index.tsx │ │ ├── groups │ │ │ ├── CreateGroup │ │ │ │ └── CreateGroup.tsx │ │ │ ├── EditGroup │ │ │ │ └── EditGroup.tsx │ │ │ ├── Group │ │ │ │ ├── EditGroupUsers │ │ │ │ │ └── EditGroupUsers.tsx │ │ │ │ ├── Group.tsx │ │ │ │ └── RemoveGroupUser │ │ │ │ │ └── RemoveGroupUser.tsx │ │ │ ├── GroupForm │ │ │ │ ├── GroupForm.tsx │ │ │ │ ├── GroupFormUsersSelect │ │ │ │ │ └── GroupFormUsersSelect.tsx │ │ │ │ └── GroupFormUsersTable │ │ │ │ │ └── GroupFormUsersTable.tsx │ │ │ ├── GroupsAdmin.tsx │ │ │ ├── GroupsList │ │ │ │ ├── GroupCard │ │ │ │ │ ├── GroupCard.tsx │ │ │ │ │ ├── GroupCardActions │ │ │ │ │ │ └── GroupCardActions.tsx │ │ │ │ │ └── GroupCardAvatars │ │ │ │ │ │ ├── GroupCardAvatars.tsx │ │ │ │ │ │ └── GroupPopover │ │ │ │ │ │ └── GroupPopover.tsx │ │ │ │ ├── GroupEmpty │ │ │ │ │ └── GroupEmpty.tsx │ │ │ │ └── GroupsList.tsx │ │ │ ├── RemoveGroup │ │ │ │ └── RemoveGroup.tsx │ │ │ └── hooks │ │ │ │ └── useGroupForm.ts │ │ ├── index.tsx │ │ ├── invoice │ │ │ ├── InvoiceAdminPage.tsx │ │ │ └── InvoiceList.tsx │ │ ├── menu │ │ │ └── AdminMenu.tsx │ │ ├── projectRoles │ │ │ ├── CreateProjectRole │ │ │ │ └── CreateProjectRole.tsx │ │ │ ├── EditProjectRole │ │ │ │ └── EditProjectRole.tsx │ │ │ ├── ProjectRoleForm │ │ │ │ ├── EnvironmentPermissionAccordion │ │ │ │ │ ├── EnvironmentPermissionAccordion.styles.ts │ │ │ │ │ └── EnvironmentPermissionAccordion.tsx │ │ │ │ ├── ProjectRoleForm.styles.ts │ │ │ │ └── ProjectRoleForm.tsx │ │ │ ├── ProjectRoles │ │ │ │ ├── ProjectRoleDeleteConfirm │ │ │ │ │ ├── ProjectRoleDeleteConfirm.styles.ts │ │ │ │ │ └── ProjectRoleDeleteConfirm.tsx │ │ │ │ ├── ProjectRoleList │ │ │ │ │ └── ProjectRoleList.tsx │ │ │ │ ├── ProjectRoles.styles.ts │ │ │ │ └── ProjectRoles.tsx │ │ │ └── hooks │ │ │ │ └── useProjectRoleForm.ts │ │ └── users │ │ │ ├── ConfirmUserAdded │ │ │ ├── ConfirmUserAdded.tsx │ │ │ ├── ConfirmUserEmail │ │ │ │ ├── ConfirmUserEmail.styles.ts │ │ │ │ └── ConfirmUserEmail.tsx │ │ │ └── ConfirmUserLink │ │ │ │ ├── ConfirmUserLink.tsx │ │ │ │ └── UserInviteLink │ │ │ │ └── UserInviteLink.tsx │ │ │ ├── CreateUser │ │ │ └── CreateUser.tsx │ │ │ ├── EditUser │ │ │ └── EditUser.tsx │ │ │ ├── UserForm │ │ │ ├── UserForm.styles.ts │ │ │ └── UserForm.tsx │ │ │ ├── UsersAdmin.tsx │ │ │ ├── UsersList │ │ │ ├── ChangePassword │ │ │ │ └── ChangePassword.tsx │ │ │ ├── DeleteUser │ │ │ │ └── DeleteUser.tsx │ │ │ ├── UserTypeCell │ │ │ │ └── UserTypeCell.tsx │ │ │ ├── UsersActionsCell │ │ │ │ └── UsersActionsCell.tsx │ │ │ └── UsersList.tsx │ │ │ ├── hooks │ │ │ └── useAddUserForm.ts │ │ │ └── util.ts │ ├── application │ │ ├── ApplicationEdit │ │ │ └── ApplicationEdit.tsx │ │ ├── ApplicationList │ │ │ └── ApplicationList.tsx │ │ ├── ApplicationUpdate │ │ │ └── ApplicationUpdate.tsx │ │ ├── ApplicationView │ │ │ └── ApplicationView.tsx │ │ └── iconNames.ts │ ├── archive │ │ ├── ArchiveTable │ │ │ ├── ArchiveTable.tsx │ │ │ ├── ArchivedFeatureActionCell │ │ │ │ ├── ArchivedFeatureActionCell.tsx │ │ │ │ └── ArchivedFeatureDeleteConfirm │ │ │ │ │ └── ArchivedFeatureDeleteConfirm.tsx │ │ │ └── FeatureArchivedCell │ │ │ │ └── FeatureArchivedCell.tsx │ │ ├── FeaturesArchiveTable.tsx │ │ ├── ProjectFeaturesArchiveTable.tsx │ │ └── RedirectArchive.tsx │ ├── common │ │ ├── AdminAlert │ │ │ └── AdminAlert.tsx │ │ ├── AnimateOnMount │ │ │ └── AnimateOnMount.tsx │ │ ├── Announcer │ │ │ ├── AnnouncerContext │ │ │ │ ├── AnnouncerContext.test.tsx │ │ │ │ └── AnnouncerContext.tsx │ │ │ ├── AnnouncerElement │ │ │ │ ├── AnnouncerElement.styles.ts │ │ │ │ └── AnnouncerElement.tsx │ │ │ └── AnnouncerProvider │ │ │ │ └── AnnouncerProvider.tsx │ │ ├── ApiError │ │ │ └── ApiError.tsx │ │ ├── AutocompleteBox │ │ │ ├── AutocompleteBox.styles.ts │ │ │ └── AutocompleteBox.tsx │ │ ├── Badge │ │ │ └── Badge.tsx │ │ ├── BreadcrumbNav │ │ │ ├── BreadcrumbNav.styles.ts │ │ │ └── BreadcrumbNav.tsx │ │ ├── CheckmarkBadge │ │ │ ├── CheckMarkBadge.styles.ts │ │ │ └── CheckMarkBadge.tsx │ │ ├── Codebox │ │ │ ├── Codebox.styles.ts │ │ │ └── Codebox.tsx │ │ ├── ConditionallyRender │ │ │ └── ConditionallyRender.tsx │ │ ├── ConstraintAccordion │ │ │ ├── ConstraintAccordion.styles.ts │ │ │ ├── ConstraintAccordion.tsx │ │ │ ├── ConstraintAccordionEdit │ │ │ │ ├── ConstraintAccordionEdit.tsx │ │ │ │ ├── ConstraintAccordionEditBody │ │ │ │ │ ├── ConstraintAccordionEditBody.styles.ts │ │ │ │ │ ├── ConstraintAccordionEditBody.tsx │ │ │ │ │ ├── ConstraintFormHeader │ │ │ │ │ │ └── ConstraintFormHeader.tsx │ │ │ │ │ ├── DateSingleValue │ │ │ │ │ │ ├── DateSingleValue.test.tsx │ │ │ │ │ │ ├── DateSingleValue.tsx │ │ │ │ │ │ └── __snapshots__ │ │ │ │ │ │ │ └── DateSingleValue.test.tsx.snap │ │ │ │ │ ├── FreeTextInput │ │ │ │ │ │ └── FreeTextInput.tsx │ │ │ │ │ ├── LegalValueLabel │ │ │ │ │ │ ├── LegalValueLabel.styles.ts │ │ │ │ │ │ └── LegalValueLabel.tsx │ │ │ │ │ ├── ResolveInput │ │ │ │ │ │ └── ResolveInput.tsx │ │ │ │ │ ├── RestrictiveLegalValues │ │ │ │ │ │ └── RestrictiveLegalValues.tsx │ │ │ │ │ ├── SingleLegalValue │ │ │ │ │ │ └── SingleLegalValue.tsx │ │ │ │ │ ├── SingleValue │ │ │ │ │ │ └── SingleValue.tsx │ │ │ │ │ └── useConstraintInput │ │ │ │ │ │ ├── constraintValidators.test.ts │ │ │ │ │ │ ├── constraintValidators.ts │ │ │ │ │ │ └── useConstraintInput.tsx │ │ │ │ ├── ConstraintAccordionEditHeader │ │ │ │ │ ├── ConstraintAccordionEditHeader.tsx │ │ │ │ │ └── helpers.ts │ │ │ │ └── StyledToggleButton │ │ │ │ │ ├── CaseSensitiveButton │ │ │ │ │ └── CaseSensitiveButton.tsx │ │ │ │ │ ├── InvertedOperatorButton │ │ │ │ │ └── InvertedOperatorButton.tsx │ │ │ │ │ └── StyledToggleButton.tsx │ │ │ ├── ConstraintAccordionHeaderActions │ │ │ │ └── ConstraintAccordionHeaderActions.tsx │ │ │ ├── ConstraintAccordionList │ │ │ │ ├── ConstraintAccordionList.styles.ts │ │ │ │ ├── ConstraintAccordionList.tsx │ │ │ │ └── createEmptyConstraint.ts │ │ │ ├── ConstraintAccordionView │ │ │ │ ├── ConstraintAccordionView.tsx │ │ │ │ ├── ConstraintAccordionViewBody │ │ │ │ │ ├── ConstraintAccordionViewBody.style.ts │ │ │ │ │ ├── ConstraintAccordionViewBody.tsx │ │ │ │ │ ├── MultipleValues │ │ │ │ │ │ └── MultipleValues.tsx │ │ │ │ │ └── SingleValue │ │ │ │ │ │ └── SingleValue.tsx │ │ │ │ └── ConstraintAccordionViewHeader │ │ │ │ │ ├── ConstraintAccordionViewHeader.tsx │ │ │ │ │ ├── ConstraintAccordionViewHeaderInfo │ │ │ │ │ └── ConstraintAccordionViewHeaderInfo.tsx │ │ │ │ │ ├── ConstraintViewHeaderOperator │ │ │ │ │ └── ConstraintViewHeaderOperator.tsx │ │ │ │ │ ├── ContraintAccordionViewHeaderMultipleValues │ │ │ │ │ └── ConstraintAccordionViewHeaderMultipleValues.tsx │ │ │ │ │ ├── ContraintAccordionViewHeaderSingleValue │ │ │ │ │ └── ConstraintAccordionViewHeaderSingleValue.tsx │ │ │ │ │ └── StyledIconWrapper │ │ │ │ │ └── StyledIconWrapper.tsx │ │ │ ├── ConstraintIcon.tsx │ │ │ ├── ConstraintOperator │ │ │ │ ├── ConstraintOperator.styles.ts │ │ │ │ ├── ConstraintOperator.tsx │ │ │ │ └── formatOperatorDescription.ts │ │ │ ├── ConstraintOperatorSelect │ │ │ │ ├── ConstraintOperatorSelect.styles.ts │ │ │ │ └── ConstraintOperatorSelect.tsx │ │ │ └── ConstraintValueSearch │ │ │ │ └── ConstraintValueSearch.tsx │ │ ├── CreateButton │ │ │ └── CreateButton.tsx │ │ ├── Dialogue │ │ │ ├── Dialogue.styles.ts │ │ │ └── Dialogue.tsx │ │ ├── DividerText │ │ │ ├── DividerText.styles.ts │ │ │ └── DividerText.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownButton │ │ │ │ └── DropdownButton.tsx │ │ │ └── DropdownMenu.tsx │ │ ├── EnvironmentIcon │ │ │ └── EnvironmentIcon.tsx │ │ ├── EnvironmentStrategiesDialog │ │ │ ├── EnvironmentStrategyDialog.styles.ts │ │ │ └── EnvironmentStrategyDialog.tsx │ │ ├── FeatureArchiveDialog │ │ │ └── FeatureArchiveDialog.tsx │ │ ├── FeatureStaleDialog │ │ │ └── FeatureStaleDialog.tsx │ │ ├── FormTemplate │ │ │ ├── FormTemplate.styles.ts │ │ │ └── FormTemplate.tsx │ │ ├── GeneralSelect │ │ │ └── GeneralSelect.tsx │ │ ├── Gradient │ │ │ └── Gradient.tsx │ │ ├── GridCol │ │ │ └── GridCol.tsx │ │ ├── GridRow │ │ │ └── GridRow.tsx │ │ ├── GuidanceIndicator │ │ │ └── GuidanceIndicator.tsx │ │ ├── HelpIcon │ │ │ ├── HelpIcon.styles.ts │ │ │ └── HelpIcon.tsx │ │ ├── Highlighter │ │ │ ├── Highlighter.styles.ts │ │ │ └── Highlighter.tsx │ │ ├── Input │ │ │ ├── Input.styles.ts │ │ │ └── Input.tsx │ │ ├── InputCaption │ │ │ └── InputCaption.tsx │ │ ├── InputListField │ │ │ └── InputListField.tsx │ │ ├── InstanceStatus │ │ │ ├── InstanceStatus.tsx │ │ │ ├── InstanceStatusBar.test.tsx │ │ │ ├── InstanceStatusBar.tsx │ │ │ └── __snapshots__ │ │ │ │ └── InstanceStatusBar.test.tsx.snap │ │ ├── Loader │ │ │ ├── Loader.styles.ts │ │ │ └── Loader.tsx │ │ ├── LoginRedirect │ │ │ └── LoginRedirect.tsx │ │ ├── MainHeader │ │ │ └── MainHeader.tsx │ │ ├── NoItems │ │ │ ├── NoItems.styles.ts │ │ │ └── NoItems.tsx │ │ ├── NotFound │ │ │ ├── NotFound.styles.ts │ │ │ └── NotFound.tsx │ │ ├── OperatorUpgradeAlert │ │ │ └── OperatorUpgradeAlert.tsx │ │ ├── PageContent │ │ │ ├── PageContent.styles.ts │ │ │ └── PageContent.tsx │ │ ├── PageHeader │ │ │ ├── PageHeader.styles.ts │ │ │ └── PageHeader.tsx │ │ ├── PaginateUI │ │ │ ├── PaginateUI.tsx │ │ │ └── PaginationUI.styles.ts │ │ ├── PasswordField │ │ │ └── PasswordField.tsx │ │ ├── PercentageCircle │ │ │ └── PercentageCircle.tsx │ │ ├── PermissionButton │ │ │ └── PermissionButton.tsx │ │ ├── PermissionHOC │ │ │ └── PermissionHOC.tsx │ │ ├── PermissionIconButton │ │ │ └── PermissionIconButton.tsx │ │ ├── PermissionSwitch │ │ │ └── PermissionSwitch.tsx │ │ ├── Proclamation │ │ │ ├── Proclamation.styles.ts │ │ │ └── Proclamation.tsx │ │ ├── ProjectSelect │ │ │ └── ProjectSelect.tsx │ │ ├── ProtectedRoute │ │ │ └── ProtectedRoute.tsx │ │ ├── ResponsiveButton │ │ │ └── ResponsiveButton.tsx │ │ ├── ScrollTop │ │ │ └── ScrollTop.tsx │ │ ├── Search │ │ │ ├── Search.styles.ts │ │ │ ├── Search.tsx │ │ │ └── SearchSuggestions │ │ │ │ ├── SearchDescription │ │ │ │ └── SearchDescription.tsx │ │ │ │ ├── SearchInstructions │ │ │ │ └── SearchInstructions.tsx │ │ │ │ └── SearchSuggestions.tsx │ │ ├── SearchField │ │ │ ├── SearchField.tsx │ │ │ └── styles.ts │ │ ├── SegmentItem │ │ │ ├── SegmentItem.styles.ts │ │ │ └── SegmentItem.tsx │ │ ├── SidebarModal │ │ │ ├── SidebarModal.styles.ts │ │ │ └── SidebarModal.tsx │ │ ├── SkipNav │ │ │ ├── SkipNavLink.styles.ts │ │ │ ├── SkipNavLink.tsx │ │ │ └── SkipNavTarget.tsx │ │ ├── StatusChip │ │ │ ├── StatusChip.styles.ts │ │ │ └── StatusChip.tsx │ │ ├── StrategyItemContainer │ │ │ ├── StrategyItemContainer.styles.ts │ │ │ └── StrategyItemContainer.tsx │ │ ├── StrategySeparator │ │ │ └── StrategySeparator.tsx │ │ ├── StringTruncator │ │ │ └── StringTruncator.tsx │ │ ├── TabNav │ │ │ ├── TabNav │ │ │ │ ├── TabNav.styles.ts │ │ │ │ └── TabNav.tsx │ │ │ └── TabPanel │ │ │ │ └── TabPanel.tsx │ │ ├── Table │ │ │ ├── SearchHighlightContext │ │ │ │ └── SearchHighlightContext.tsx │ │ │ ├── SortableTableHeader │ │ │ │ ├── CellSortable │ │ │ │ │ ├── CellSortable.styles.ts │ │ │ │ │ ├── CellSortable.tsx │ │ │ │ │ └── SortArrow │ │ │ │ │ │ ├── SortArrow.styles.ts │ │ │ │ │ │ └── SortArrow.tsx │ │ │ │ ├── SortableTableHeader.styles.ts │ │ │ │ └── SortableTableHeader.tsx │ │ │ ├── Table │ │ │ │ ├── Table.styles.ts │ │ │ │ └── Table.tsx │ │ │ ├── TableCell │ │ │ │ ├── TableCell.styles.ts │ │ │ │ └── TableCell.tsx │ │ │ ├── TablePlaceholder │ │ │ │ ├── TablePlaceholder.styles.ts │ │ │ │ └── TablePlaceholder.tsx │ │ │ ├── VirtualizedTable │ │ │ │ ├── VirtualizedTable.styles.ts │ │ │ │ └── VirtualizedTable.tsx │ │ │ ├── cells │ │ │ │ ├── ActionCell │ │ │ │ │ ├── ActionCell.styles.ts │ │ │ │ │ └── ActionCell.tsx │ │ │ │ ├── DateCell │ │ │ │ │ └── DateCell.tsx │ │ │ │ ├── FeatureNameCell │ │ │ │ │ └── FeatureNameCell.tsx │ │ │ │ ├── FeatureSeenCell │ │ │ │ │ ├── FeatureSeenCell.styles.ts │ │ │ │ │ └── FeatureSeenCell.tsx │ │ │ │ ├── FeatureTypeCell │ │ │ │ │ ├── FeatureTypeCell.styles.ts │ │ │ │ │ └── FeatureTypeCell.tsx │ │ │ │ ├── HighlightCell │ │ │ │ │ ├── HighlightCell.styles.ts │ │ │ │ │ └── HighlightCell.tsx │ │ │ │ ├── IconCell │ │ │ │ │ └── IconCell.tsx │ │ │ │ ├── LinkCell │ │ │ │ │ ├── LinkCell.styles.ts │ │ │ │ │ └── LinkCell.tsx │ │ │ │ ├── TextCell │ │ │ │ │ ├── TextCell.styles.ts │ │ │ │ │ └── TextCell.tsx │ │ │ │ └── TimeAgoCell │ │ │ │ │ └── TimeAgoCell.tsx │ │ │ └── index.ts │ │ ├── TagSelect │ │ │ └── TagSelect.tsx │ │ ├── ThemeMode │ │ │ └── ThemeMode.tsx │ │ ├── ToastRenderer │ │ │ ├── Toast │ │ │ │ ├── Toast.styles.ts │ │ │ │ └── Toast.tsx │ │ │ ├── ToastRenderer.styles.ts │ │ │ └── ToastRenderer.tsx │ │ ├── TooltipResolver │ │ │ └── TooltipResolver.tsx │ │ ├── UpdateButton │ │ │ └── UpdateButton.tsx │ │ ├── UserAvatar │ │ │ └── UserAvatar.tsx │ │ ├── common.module.scss │ │ ├── flags.ts │ │ ├── index.jsx │ │ ├── select.tsx │ │ └── util.ts │ ├── context │ │ ├── ContectFormChip │ │ │ ├── ContextFormChip.styles.ts │ │ │ ├── ContextFormChip.tsx │ │ │ ├── ContextFormChipList.styles.ts │ │ │ └── ContextFormChipList.tsx │ │ ├── ContextForm │ │ │ ├── ContextForm.styles.ts │ │ │ └── ContextForm.tsx │ │ ├── ContextList │ │ │ ├── AddContextButton │ │ │ │ └── AddContextButton.tsx │ │ │ ├── ContextActionsCell │ │ │ │ └── ContextActionsCell.tsx │ │ │ └── ContextList.tsx │ │ ├── CreateUnleashContext │ │ │ ├── CreateUnleashContext.tsx │ │ │ └── CreateUnleashContextPage.tsx │ │ ├── EditContext │ │ │ └── EditContext.tsx │ │ └── hooks │ │ │ └── useContextForm.ts │ ├── environments │ │ ├── CreateEnvironment │ │ │ └── CreateEnvironment.tsx │ │ ├── CreateEnvironmentButton │ │ │ └── CreateEnvironmentButton.tsx │ │ ├── EditEnvironment │ │ │ └── EditEnvironment.tsx │ │ ├── EnvironmentCard │ │ │ ├── EnvironmentCard.styles.ts │ │ │ └── EnvironmentCard.tsx │ │ ├── EnvironmentDeleteConfirm │ │ │ ├── EnvironmentDeleteConfirm.styles.ts │ │ │ └── EnvironmentDeleteConfirm.tsx │ │ ├── EnvironmentForm │ │ │ ├── EnvironmentForm.styles.ts │ │ │ ├── EnvironmentForm.tsx │ │ │ └── EnvironmentTypeSelector │ │ │ │ ├── EnvironmentTypeSelector.styles.ts │ │ │ │ └── EnvironmentTypeSelector.tsx │ │ ├── EnvironmentTable │ │ │ ├── EnvironmentActionCell │ │ │ │ └── EnvironmentActionCell.tsx │ │ │ ├── EnvironmentIconCell │ │ │ │ └── EnvironmentIconCell.tsx │ │ │ ├── EnvironmentNameCell │ │ │ │ └── EnvironmentNameCell.tsx │ │ │ ├── EnvironmentRow │ │ │ │ └── EnvironmentRow.tsx │ │ │ └── EnvironmentTable.tsx │ │ ├── EnvironmentToggleConfirm │ │ │ └── EnvironmentToggleConfirm.tsx │ │ └── hooks │ │ │ └── useEnvironmentForm.ts │ ├── events │ │ ├── EventCard │ │ │ └── EventCard.tsx │ │ ├── EventDiff │ │ │ └── EventDiff.tsx │ │ ├── EventJson │ │ │ └── EventJson.tsx │ │ ├── EventLog │ │ │ └── EventLog.tsx │ │ └── EventPage │ │ │ └── EventPage.tsx │ ├── feature │ │ ├── CopyFeature │ │ │ ├── CopyFeature.module.scss │ │ │ └── CopyFeature.tsx │ │ ├── CreateFeature │ │ │ └── CreateFeature.tsx │ │ ├── CreateFeatureButton │ │ │ ├── CreateFeatureButton.tsx │ │ │ └── useCreateFeaturePath.ts │ │ ├── EditFeature │ │ │ └── EditFeature.tsx │ │ ├── FeatureForm │ │ │ ├── FeatureForm.styles.ts │ │ │ └── FeatureForm.tsx │ │ ├── FeatureStrategy │ │ │ ├── FeatureStrategyConstraints │ │ │ │ └── FeatureStrategyConstraints.tsx │ │ │ ├── FeatureStrategyCreate │ │ │ │ ├── FeatureStrategyCreate.test.tsx │ │ │ │ └── FeatureStrategyCreate.tsx │ │ │ ├── FeatureStrategyEdit │ │ │ │ ├── FeatureStrategyEdit.test.tsx │ │ │ │ └── FeatureStrategyEdit.tsx │ │ │ ├── FeatureStrategyEmpty │ │ │ │ ├── CopyButton │ │ │ │ │ └── CopyButton.tsx │ │ │ │ ├── FeatureStrategyEmpty.styles.ts │ │ │ │ ├── FeatureStrategyEmpty.tsx │ │ │ │ └── PresetCard │ │ │ │ │ └── PresetCard.tsx │ │ │ ├── FeatureStrategyEnabled │ │ │ │ └── FeatureStrategyEnabled.tsx │ │ │ ├── FeatureStrategyForm │ │ │ │ ├── FeatureStrategyForm.styles.ts │ │ │ │ └── FeatureStrategyForm.tsx │ │ │ ├── FeatureStrategyIcon │ │ │ │ └── FeatureStrategyIcon.tsx │ │ │ ├── FeatureStrategyIcons │ │ │ │ └── FeatureStrategyIcons.tsx │ │ │ ├── FeatureStrategyMenu │ │ │ │ ├── FeatureStrategyMenu.tsx │ │ │ │ ├── FeatureStrategyMenuCard │ │ │ │ │ ├── FeatureStrategyMenuCard.styles.ts │ │ │ │ │ └── FeatureStrategyMenuCard.tsx │ │ │ │ └── FeatureStrategyMenuCards │ │ │ │ │ └── FeatureStrategyMenuCards.tsx │ │ │ ├── FeatureStrategyProdGuard │ │ │ │ └── FeatureStrategyProdGuard.tsx │ │ │ ├── FeatureStrategyRemove │ │ │ │ └── FeatureStrategyRemove.tsx │ │ │ ├── FeatureStrategySegment │ │ │ │ ├── FeatureStrategySegment.styles.ts │ │ │ │ ├── FeatureStrategySegment.tsx │ │ │ │ ├── FeatureStrategySegmentChip.styles.ts │ │ │ │ ├── FeatureStrategySegmentChip.tsx │ │ │ │ ├── FeatureStrategySegmentList.styles.ts │ │ │ │ └── FeatureStrategySegmentList.tsx │ │ │ └── FeatureStrategyType │ │ │ │ └── FeatureStrategyType.tsx │ │ ├── FeatureToggleList │ │ │ ├── FeatureStaleCell │ │ │ │ ├── FeatureStaleCell.styles.ts │ │ │ │ └── FeatureStaleCell.tsx │ │ │ ├── FeatureToggleListItem │ │ │ │ ├── FeatureToggleListItem.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── FeatureToggleListItem.test.jsx.snap │ │ │ │ └── styles.ts │ │ │ ├── FeatureToggleListTable.tsx │ │ │ └── __snapshots__ │ │ │ │ └── FeatureToggleList.test.jsx.snap │ │ ├── FeatureView │ │ │ ├── FeatureLog │ │ │ │ ├── FeatureLog.styles.ts │ │ │ │ └── FeatureLog.tsx │ │ │ ├── FeatureMetrics │ │ │ │ ├── FeatureMetrics.styles.ts │ │ │ │ ├── FeatureMetrics.tsx │ │ │ │ ├── FeatureMetricsChart │ │ │ │ │ ├── FeatureMetricsChart.tsx │ │ │ │ │ ├── createChartData.ts │ │ │ │ │ └── createChartOptions.ts │ │ │ │ ├── FeatureMetricsChips │ │ │ │ │ ├── FeatureMetricsChips.styles.ts │ │ │ │ │ └── FeatureMetricsChips.tsx │ │ │ │ ├── FeatureMetricsContent │ │ │ │ │ └── FeatureMetricsContent.tsx │ │ │ │ ├── FeatureMetricsHours │ │ │ │ │ └── FeatureMetricsHours.tsx │ │ │ │ ├── FeatureMetricsStats │ │ │ │ │ ├── FeatureMetricsStats.styles.ts │ │ │ │ │ ├── FeatureMetricsStats.tsx │ │ │ │ │ └── FeatureMetricsStatsRaw.tsx │ │ │ │ └── FeatureMetricsTable │ │ │ │ │ └── FeatureMetricsTable.tsx │ │ │ ├── FeatureNotFound │ │ │ │ ├── FeatureNotFound.styles.ts │ │ │ │ └── FeatureNotFound.tsx │ │ │ ├── FeatureOverview │ │ │ │ ├── AddTagDialog │ │ │ │ │ ├── AddTagDialog.styles.ts │ │ │ │ │ └── AddTagDialog.tsx │ │ │ │ ├── FeatureOverview.styles.ts │ │ │ │ ├── FeatureOverview.tsx │ │ │ │ ├── FeatureOverviewEnvSwitches │ │ │ │ │ ├── FeatureOverviewEnvSwitch │ │ │ │ │ │ ├── FeatureOverviewEnvSwitch.styles.ts │ │ │ │ │ │ └── FeatureOverviewEnvSwitch.tsx │ │ │ │ │ └── FeatureOverviewEnvSwitches.tsx │ │ │ │ ├── FeatureOverviewEnvironments │ │ │ │ │ ├── FeatureOverviewEnvironment │ │ │ │ │ │ ├── EnvironmentAccordionBody │ │ │ │ │ │ │ ├── EnvironmentAccordionBody.styles.ts │ │ │ │ │ │ │ ├── EnvironmentAccordionBody.tsx │ │ │ │ │ │ │ └── StrategyDraggableItem │ │ │ │ │ │ │ │ ├── StrategyDraggableItem.tsx │ │ │ │ │ │ │ │ └── StrategyItem │ │ │ │ │ │ │ │ ├── CopyStrategyIconMenu │ │ │ │ │ │ │ │ └── CopyStrategyIconMenu.tsx │ │ │ │ │ │ │ │ ├── StrategyExecution │ │ │ │ │ │ │ │ ├── ConstraintItem │ │ │ │ │ │ │ │ │ ├── ConstraintItem.styles.ts │ │ │ │ │ │ │ │ │ └── ConstraintItem.tsx │ │ │ │ │ │ │ │ ├── StrategyExecution.styles.ts │ │ │ │ │ │ │ │ └── StrategyExecution.tsx │ │ │ │ │ │ │ │ └── StrategyItem.tsx │ │ │ │ │ │ ├── EnvironmentFooter │ │ │ │ │ │ │ └── EnvironmentFooter.tsx │ │ │ │ │ │ ├── FeatureOverviewEnvironment.styles.ts │ │ │ │ │ │ ├── FeatureOverviewEnvironment.tsx │ │ │ │ │ │ ├── FeatureOverviewEnvironmentMetrics │ │ │ │ │ │ │ ├── FeatureOverviewEnvironmentMetrics.styles.ts │ │ │ │ │ │ │ └── FeatureOverviewEnvironmentMetrics.tsx │ │ │ │ │ │ └── SectionSeparator │ │ │ │ │ │ │ └── SectionSeparator.tsx │ │ │ │ │ └── FeatureOverviewEnvironments.tsx │ │ │ │ ├── FeatureOverviewMetaData │ │ │ │ │ ├── FeatureOverviewMetaData.tsx │ │ │ │ │ ├── FeatureOverviewMetadata.styles.ts │ │ │ │ │ └── FeatureOverviewTags │ │ │ │ │ │ ├── FeatureOverviewTags.styles.ts │ │ │ │ │ │ └── FeatureOverviewTags.tsx │ │ │ │ └── FeatureOverviewSegment │ │ │ │ │ └── FeatureOverviewSegment.tsx │ │ │ ├── FeatureSettings │ │ │ │ ├── FeatureSettings.styles.ts │ │ │ │ ├── FeatureSettings.tsx │ │ │ │ ├── FeatureSettingsInformation │ │ │ │ │ ├── FeatureSettingsInformation.style.ts │ │ │ │ │ └── FeatureSettingsInformation.tsx │ │ │ │ ├── FeatureSettingsMetadata │ │ │ │ │ └── FeatureTypeSelect │ │ │ │ │ │ └── FeatureTypeSelect.tsx │ │ │ │ └── FeatureSettingsProject │ │ │ │ │ ├── FeatureProjectSelect │ │ │ │ │ └── FeatureProjectSelect.tsx │ │ │ │ │ ├── FeatureSettingsProject.tsx │ │ │ │ │ └── FeatureSettingsProjectConfirm │ │ │ │ │ ├── FeatureSettingsProjectConfirm.styles.ts │ │ │ │ │ └── FeatureSettingsProjectConfirm.tsx │ │ │ ├── FeatureStatus │ │ │ │ ├── FeatureStatus.styles.ts │ │ │ │ └── FeatureStatus.tsx │ │ │ ├── FeatureType │ │ │ │ ├── FeatureType.styles.ts │ │ │ │ └── FeatureType.tsx │ │ │ ├── FeatureVariants │ │ │ │ ├── FeatureVariants.styles.ts │ │ │ │ ├── FeatureVariants.tsx │ │ │ │ └── FeatureVariantsList │ │ │ │ │ ├── AddFeatureVariant │ │ │ │ │ ├── AddFeatureVariant.styles.ts │ │ │ │ │ ├── AddFeatureVariant.tsx │ │ │ │ │ ├── OverrideConfig │ │ │ │ │ │ ├── OverrideConfig.styles.ts │ │ │ │ │ │ └── OverrideConfig.tsx │ │ │ │ │ ├── enums.ts │ │ │ │ │ ├── useOverrides.test.ts │ │ │ │ │ └── useOverrides.ts │ │ │ │ │ ├── FeatureVariantsList.tsx │ │ │ │ │ ├── PayloadOverridesCell │ │ │ │ │ └── PayloadOverridesCell.tsx │ │ │ │ │ ├── VariantsActionsCell │ │ │ │ │ └── VariantsActionsCell.tsx │ │ │ │ │ └── useDeleteVariantMarkup.tsx │ │ │ ├── FeatureView.styles.ts │ │ │ └── FeatureView.tsx │ │ ├── RedirectFeatureView │ │ │ └── RedirectFeatureView.tsx │ │ ├── StrategyTypes │ │ │ ├── DefaultStrategy │ │ │ │ └── DefaultStrategy.tsx │ │ │ ├── FlexibleStrategy │ │ │ │ └── FlexibleStrategy.tsx │ │ │ ├── GeneralStrategy │ │ │ │ └── GeneralStrategy.tsx │ │ │ ├── RolloutSlider │ │ │ │ └── RolloutSlider.tsx │ │ │ ├── StrategyInputList │ │ │ │ └── StrategyInputList.tsx │ │ │ ├── StrategyParameter │ │ │ │ └── StrategyParameter.tsx │ │ │ └── UserWithIdStrategy │ │ │ │ └── UserWithId.tsx │ │ └── hooks │ │ │ └── useFeatureForm.ts │ ├── feedback │ │ ├── FeedbackCES │ │ │ ├── FeedbackCES.styles.ts │ │ │ ├── FeedbackCES.tsx │ │ │ ├── FeedbackCESForm.styles.ts │ │ │ ├── FeedbackCESForm.test.tsx │ │ │ ├── FeedbackCESForm.tsx │ │ │ ├── FeedbackCESScore.styles.ts │ │ │ ├── FeedbackCESScore.tsx │ │ │ ├── __snapshots__ │ │ │ │ └── FeedbackCESForm.test.tsx.snap │ │ │ └── sendFeedbackInput.ts │ │ ├── FeedbackCESContext │ │ │ ├── FeedbackCESContext.ts │ │ │ ├── FeedbackCESProvider.tsx │ │ │ ├── useFeedbackCESEnabled.ts │ │ │ └── useFeedbackCESSeen.ts │ │ └── FeedbackNPS │ │ │ ├── FeedbackNPS.styles.ts │ │ │ ├── FeedbackNPS.tsx │ │ │ └── showNPSFeedback.ts │ ├── layout │ │ ├── LayoutPicker │ │ │ └── LayoutPicker.tsx │ │ └── MainLayout │ │ │ └── MainLayout.tsx │ ├── menu │ │ ├── Footer │ │ │ ├── ApiDetails │ │ │ │ ├── ApiDetails.test.tsx │ │ │ │ ├── ApiDetails.tsx │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── ApiDetails.test.tsx.snap │ │ │ │ └── apidetails.helpers.tsx │ │ │ ├── Footer.styles.ts │ │ │ ├── Footer.test.tsx │ │ │ ├── Footer.tsx │ │ │ ├── FooterTitle.styles.ts │ │ │ ├── FooterTitle.tsx │ │ │ └── __snapshots__ │ │ │ │ └── Footer.test.tsx.snap │ │ ├── Header │ │ │ ├── DrawerMenu │ │ │ │ ├── DrawerMenu.module.scss │ │ │ │ └── DrawerMenu.tsx │ │ │ ├── Header.styles.ts │ │ │ ├── Header.tsx │ │ │ ├── NavigationLink │ │ │ │ ├── NavigationLink.styles.ts │ │ │ │ └── NavigationLink.tsx │ │ │ └── NavigationMenu │ │ │ │ └── NavigationMenu.tsx │ │ ├── __tests__ │ │ │ ├── __snapshots__ │ │ │ │ └── routes.test.tsx.snap │ │ │ └── routes.test.tsx │ │ └── routes.ts │ ├── playground │ │ └── Playground │ │ │ ├── LazyPlayground.tsx │ │ │ ├── Playground.tsx │ │ │ ├── PlaygroundForm │ │ │ ├── PlaygroundCodeFieldset │ │ │ │ ├── PlaygroundCodeFieldset.tsx │ │ │ │ └── PlaygroundEditor │ │ │ │ │ └── PlaygroundEditor.tsx │ │ │ ├── PlaygroundConnectionFieldset │ │ │ │ └── PlaygroundConnectionFieldset.tsx │ │ │ └── PlaygroundForm.tsx │ │ │ ├── PlaygroundGuidance │ │ │ ├── PlaygroundGuidance.tsx │ │ │ └── PlaygroundGuidanceSection │ │ │ │ └── PlaygroundGuidanceSection.tsx │ │ │ ├── PlaygroundGuidancePopper │ │ │ └── PlaygroundGuidancePopper.tsx │ │ │ ├── PlaygroundResultsTable │ │ │ ├── ContextBanner │ │ │ │ └── ContextBanner.tsx │ │ │ ├── FeatureResultInfoPopoverCell │ │ │ │ ├── FeatureDetails │ │ │ │ │ ├── FeatureDetails.styles.ts │ │ │ │ │ ├── FeatureDetails.tsx │ │ │ │ │ └── helpers.ts │ │ │ │ ├── FeatureResultInfoPopoverCell.styles.ts │ │ │ │ ├── FeatureResultInfoPopoverCell.tsx │ │ │ │ └── FeatureStrategyList │ │ │ │ │ ├── PlaygroundResultFeatureStrategyList.tsx │ │ │ │ │ └── StrategyList │ │ │ │ │ ├── StrategyItem │ │ │ │ │ ├── FeatureStrategyItem.styles.ts │ │ │ │ │ ├── FeatureStrategyItem.tsx │ │ │ │ │ └── StrategyExecution │ │ │ │ │ │ ├── ConstraintExecution │ │ │ │ │ │ ├── ConstraintError │ │ │ │ │ │ │ └── ConstraintError.tsx │ │ │ │ │ │ ├── ConstraintExecution.tsx │ │ │ │ │ │ └── ConstraintOk │ │ │ │ │ │ │ └── ConstraintOk.tsx │ │ │ │ │ │ ├── CustomStrategyParams │ │ │ │ │ │ ├── CustomParameterItem │ │ │ │ │ │ │ └── CustomParameterItem.tsx │ │ │ │ │ │ └── CustomStrategyParams.tsx │ │ │ │ │ │ ├── PlaygroundParameterItem │ │ │ │ │ │ ├── PlaygroundParameterItem.tsx │ │ │ │ │ │ └── PlaygroundParametertem.styles.ts │ │ │ │ │ │ ├── SegmentExecution │ │ │ │ │ │ ├── SegmentExecution.styles.ts │ │ │ │ │ │ └── SegmentExecution.tsx │ │ │ │ │ │ ├── StrategyExecution.styles.ts │ │ │ │ │ │ ├── StrategyExecution.tsx │ │ │ │ │ │ ├── StrategyExecutionParameters │ │ │ │ │ │ └── StrategyExecutionParameters.tsx │ │ │ │ │ │ └── helpers.ts │ │ │ │ │ └── playgroundResultStrategyLists.tsx │ │ │ ├── FeatureStatusCell │ │ │ │ └── FeatureStatusCell.tsx │ │ │ ├── PlaygroundResultChip │ │ │ │ └── PlaygroundResultChip.tsx │ │ │ ├── PlaygroundResultsTable.tsx │ │ │ └── VariantCell │ │ │ │ ├── VariantCell.tsx │ │ │ │ └── VariantInformation │ │ │ │ └── VariantInformation.tsx │ │ │ ├── interfaces │ │ │ └── playground.model.ts │ │ │ └── playground.utils.ts │ ├── project │ │ ├── Project │ │ │ ├── CreateProject │ │ │ │ └── CreateProject.tsx │ │ │ ├── DeleteProject │ │ │ │ └── DeleteProjectDialogue.tsx │ │ │ ├── EditProject │ │ │ │ └── EditProject.tsx │ │ │ ├── Project.styles.ts │ │ │ ├── Project.tsx │ │ │ ├── ProjectFeatureToggles │ │ │ │ ├── ActionsCell │ │ │ │ │ ├── ActionsCell.styles.ts │ │ │ │ │ └── ActionsCell.tsx │ │ │ │ ├── ColumnsMenu │ │ │ │ │ ├── ColumnsMenu.styles.ts │ │ │ │ │ └── ColumnsMenu.tsx │ │ │ │ ├── FeatureToggleSwitch │ │ │ │ │ ├── FeatureToggleSwitch.styles.ts │ │ │ │ │ ├── FeatureToggleSwitch.tsx │ │ │ │ │ └── hooks │ │ │ │ │ │ ├── useOptimisticUpdate.test.ts │ │ │ │ │ │ └── useOptimisticUpdate.ts │ │ │ │ ├── ProjectFeatureToggles.styles.ts │ │ │ │ ├── ProjectFeatureToggles.tsx │ │ │ │ └── hooks │ │ │ │ │ └── useEnvironmentsRef.ts │ │ │ ├── ProjectFeaturesArchive │ │ │ │ └── ProjectFeaturesArchive.tsx │ │ │ ├── ProjectForm │ │ │ │ ├── ProjectForm.styles.ts │ │ │ │ └── ProjectForm.tsx │ │ │ ├── ProjectHealth │ │ │ │ ├── ProjectHealth.tsx │ │ │ │ └── ReportTable │ │ │ │ │ ├── ReportCard │ │ │ │ │ └── ReportCard.tsx │ │ │ │ │ ├── ReportExpiredCell │ │ │ │ │ ├── ReportExpiredCell.tsx │ │ │ │ │ └── formatExpiredAt.ts │ │ │ │ │ ├── ReportStatusCell │ │ │ │ │ ├── ReportStatusCell.tsx │ │ │ │ │ └── formatStatus.ts │ │ │ │ │ ├── ReportTable.tsx │ │ │ │ │ └── utils.ts │ │ │ ├── ProjectInfo │ │ │ │ ├── ProjectInfo.styles.ts │ │ │ │ └── ProjectInfo.tsx │ │ │ ├── ProjectOverview.tsx │ │ │ └── hooks │ │ │ │ └── useProjectForm.ts │ │ ├── ProjectAccess │ │ │ ├── ProjectAccess.styles.ts │ │ │ ├── ProjectAccess.tsx │ │ │ ├── ProjectAccessAssign │ │ │ │ ├── ProjectAccessAssign.tsx │ │ │ │ └── ProjectRoleDescription │ │ │ │ │ └── ProjectRoleDescription.tsx │ │ │ ├── ProjectAccessCreate │ │ │ │ └── ProjectAccessCreate.tsx │ │ │ ├── ProjectAccessEditGroup │ │ │ │ └── ProjectAccessEditGroup.tsx │ │ │ ├── ProjectAccessEditUser │ │ │ │ └── ProjectAccessEditUser.tsx │ │ │ ├── ProjectAccessTable │ │ │ │ ├── ProjectAccessRoleCell │ │ │ │ │ └── ProjectAccessRoleCell.tsx │ │ │ │ └── ProjectAccessTable.tsx │ │ │ └── ProjectGroupView │ │ │ │ └── ProjectGroupView.tsx │ │ ├── ProjectCard │ │ │ ├── ProjectCard.styles.ts │ │ │ └── ProjectCard.tsx │ │ ├── ProjectEnvironment │ │ │ ├── EnvironmentDisableConfirm │ │ │ │ ├── EnvironmentDisableConfirm.styles.ts │ │ │ │ └── EnvironmentDisableConfirm.tsx │ │ │ ├── ProjectEnvironment.styles.ts │ │ │ ├── ProjectEnvironment.tsx │ │ │ ├── getEnabledEnvs.test.ts │ │ │ └── helpers.ts │ │ └── ProjectList │ │ │ ├── ProjectList.styles.ts │ │ │ ├── ProjectList.tsx │ │ │ └── loadingData.ts │ ├── providers │ │ ├── AccessProvider │ │ │ ├── AccessProvider.tsx │ │ │ ├── AccessProviderMock.tsx │ │ │ └── permissions.ts │ │ ├── SWRProvider │ │ │ └── SWRProvider.tsx │ │ └── UIProvider │ │ │ ├── UIProvider.tsx │ │ │ └── UIProviderContainer.tsx │ ├── segments │ │ ├── CreateSegment │ │ │ └── CreateSegment.tsx │ │ ├── CreateSegmentButton │ │ │ └── CreateSegmentButton.tsx │ │ ├── EditSegment │ │ │ └── EditSegment.tsx │ │ ├── EditSegmentButton │ │ │ └── EditSegmentButton.tsx │ │ ├── RemoveSegmentButton │ │ │ └── RemoveSegmentButton.tsx │ │ ├── SegmentActionCell │ │ │ └── SegmentActionCell.tsx │ │ ├── SegmentDelete │ │ │ ├── SegmentDelete.tsx │ │ │ ├── SegmentDeleteConfirm │ │ │ │ ├── SegmentDeleteConfirm.styles.ts │ │ │ │ └── SegmentDeleteConfirm.tsx │ │ │ └── SegmentDeleteUsedSegment │ │ │ │ └── SegmentDeleteUsedSegment.tsx │ │ ├── SegmentDocs │ │ │ └── SegmentDocs.tsx │ │ ├── SegmentEmpty │ │ │ ├── SegmentEmpty.styles.ts │ │ │ └── SegmentEmpty.tsx │ │ ├── SegmentForm │ │ │ ├── SegmentForm.styles.ts │ │ │ └── SegmentForm.tsx │ │ ├── SegmentFormStepList │ │ │ ├── SegmentFormStepList.styles.ts │ │ │ └── SegmentFormStepList.tsx │ │ ├── SegmentFormStepOne │ │ │ ├── SegmentFormStepOne.styles.ts │ │ │ └── SegmentFormStepOne.tsx │ │ ├── SegmentFormStepTwo │ │ │ ├── SegmentFormStepTwo.styles.ts │ │ │ └── SegmentFormStepTwo.tsx │ │ ├── SegmentTable │ │ │ └── SegmentTable.tsx │ │ └── hooks │ │ │ ├── useSegmentForm.ts │ │ │ └── useSegmentValuesCount.ts │ ├── splash │ │ ├── SplashPage │ │ │ └── SplashPage.tsx │ │ ├── SplashPageOperators │ │ │ ├── SplashPageOperators.styles.tsx │ │ │ └── SplashPageOperators.tsx │ │ ├── SplashPageRedirect │ │ │ └── SplashPageRedirect.tsx │ │ └── splash.tsx │ ├── strategies │ │ ├── CreateStrategy │ │ │ └── CreateStrategy.tsx │ │ ├── EditStrategy │ │ │ └── EditStrategy.tsx │ │ ├── StrategiesList │ │ │ ├── AddStrategyButton │ │ │ │ └── AddStrategyButton.tsx │ │ │ ├── StrategiesList.tsx │ │ │ ├── StrategyDeleteButton │ │ │ │ └── StrategyDeleteButton.tsx │ │ │ ├── StrategyEditButton │ │ │ │ └── StrategyEditButton.tsx │ │ │ └── StrategySwitch │ │ │ │ └── StrategySwitch.tsx │ │ ├── StrategyForm │ │ │ ├── StrategyForm.styles.ts │ │ │ ├── StrategyForm.tsx │ │ │ └── StrategyParameters │ │ │ │ ├── StrategyParameter │ │ │ │ ├── StrategyParameter.styles.ts │ │ │ │ └── StrategyParameter.tsx │ │ │ │ └── StrategyParameters.tsx │ │ ├── StrategyView │ │ │ ├── StrategyDetails │ │ │ │ └── StrategyDetails.tsx │ │ │ └── StrategyView.tsx │ │ ├── TogglesLinkList │ │ │ └── TogglesLinkList.tsx │ │ ├── hooks │ │ │ └── useStrategyForm.ts │ │ └── strategies.module.scss │ ├── tags │ │ ├── CreateTagType │ │ │ └── CreateTagType.tsx │ │ ├── EditTagType │ │ │ └── EditTagType.tsx │ │ ├── TagTypeForm │ │ │ ├── TagTypeForm.styles.ts │ │ │ ├── TagTypeForm.tsx │ │ │ └── useTagTypeForm.ts │ │ └── TagTypeList │ │ │ ├── AddTagTypeButton │ │ │ └── AddTagTypeButton.tsx │ │ │ ├── TagTypeList.tsx │ │ │ └── __tests__ │ │ │ ├── TagTypeList.test.tsx │ │ │ └── __snapshots__ │ │ │ └── TagTypeList.test.tsx.snap │ └── user │ │ ├── Authentication │ │ ├── Authentication.test.tsx │ │ └── Authentication.tsx │ │ ├── AuthenticationCustomComponent.tsx │ │ ├── DemoAuth │ │ ├── DemoAuth.module.scss │ │ └── DemoAuth.tsx │ │ ├── ForgottenPassword │ │ ├── ForgottenPassword.styles.ts │ │ ├── ForgottenPassword.test.tsx │ │ └── ForgottenPassword.tsx │ │ ├── HostedAuth │ │ ├── HostedAuth.styles.ts │ │ └── HostedAuth.tsx │ │ ├── Login │ │ ├── Login.styles.ts │ │ ├── Login.tsx │ │ ├── parseRedirectParam.test.ts │ │ └── parseRedirectParam.ts │ │ ├── NewUser │ │ ├── NewUser.styles.ts │ │ └── NewUser.tsx │ │ ├── PasswordAuth │ │ ├── PasswordAuth.styles.ts │ │ └── PasswordAuth.tsx │ │ ├── ResetPassword │ │ ├── ResetPassword.styles.ts │ │ ├── ResetPassword.test.tsx │ │ └── ResetPassword.tsx │ │ ├── SimpleAuth │ │ ├── SimpleAuth.module.scss │ │ └── SimpleAuth.tsx │ │ ├── StandaloneBanner │ │ ├── StandaloneBanner.styles.ts │ │ └── StandaloneBanner.tsx │ │ ├── UserProfile │ │ ├── EditProfile │ │ │ ├── EditProfile.styles.ts │ │ │ └── EditProfile.tsx │ │ ├── UserProfile.styles.ts │ │ ├── UserProfile.tsx │ │ ├── UserProfileContent │ │ │ ├── UserProfileContent.styles.ts │ │ │ └── UserProfileContent.tsx │ │ └── index.tsx │ │ ├── common │ │ ├── AuthOptions │ │ │ └── AuthOptions.tsx │ │ ├── InvalidToken │ │ │ └── InvalidToken.tsx │ │ ├── ResetPasswordDetails │ │ │ └── ResetPasswordDetails.tsx │ │ ├── ResetPasswordError │ │ │ └── ResetPasswordError.tsx │ │ ├── ResetPasswordForm │ │ │ ├── PasswordChecker │ │ │ │ ├── PasswordChecker.styles.ts │ │ │ │ └── PasswordChecker.tsx │ │ │ ├── PasswordMatcher │ │ │ │ ├── PasswordMatcher.styles.ts │ │ │ │ └── PasswordMatcher.tsx │ │ │ ├── ResetPasswordForm.styles.ts │ │ │ └── ResetPasswordForm.tsx │ │ ├── ResetPasswordSuccess │ │ │ └── ResetPasswordSuccess.tsx │ │ ├── SecondaryLoginActions │ │ │ ├── SecondaryLoginActions.styles.ts │ │ │ └── SecondaryLoginActions.tsx │ │ └── StandaloneLayout │ │ │ ├── StandaloneLayout.styles.ts │ │ │ └── StandaloneLayout.tsx │ │ └── user.module.scss ├── constants │ ├── apiErrors.ts │ ├── authTypes.ts │ ├── environmentTypes.ts │ ├── featureToggleTypes.ts │ ├── misc.ts │ ├── navigate.ts │ ├── operators.ts │ └── statusCodes.ts ├── contexts │ ├── AccessContext.ts │ └── UIContext.ts ├── hooks │ ├── __snapshots__ │ │ └── useFeaturesFilter.test.ts.snap │ ├── api │ │ ├── actions │ │ │ ├── useAddonsApi │ │ │ │ └── useAddonsApi.ts │ │ │ ├── useAdminUsersApi │ │ │ │ ├── errorHandlers.ts │ │ │ │ └── useAdminUsersApi.ts │ │ │ ├── useApi │ │ │ │ └── useApi.ts │ │ │ ├── useApiTokensApi │ │ │ │ └── useApiTokensApi.ts │ │ │ ├── useApplicationsApi │ │ │ │ └── useApplicationsApi.ts │ │ │ ├── useAuthApi │ │ │ │ └── useAuthApi.tsx │ │ │ ├── useAuthFeedbackApi │ │ │ │ └── useAuthFeedbackApi.ts │ │ │ ├── useAuthSettingsApi │ │ │ │ └── useAuthSettingsApi.ts │ │ │ ├── useContextsApi │ │ │ │ └── useContextsApi.ts │ │ │ ├── useEnvironmentApi │ │ │ │ └── useEnvironmentApi.ts │ │ │ ├── useFeatureApi │ │ │ │ └── useFeatureApi.ts │ │ │ ├── useFeatureArchiveApi │ │ │ │ └── useReviveFeatureApi.ts │ │ │ ├── useFeatureStrategyApi │ │ │ │ └── useFeatureStrategyApi.ts │ │ │ ├── useGroupApi │ │ │ │ └── useGroupApi.ts │ │ │ ├── useInstanceStatusApi │ │ │ │ └── useInstanceStatusApi.ts │ │ │ ├── usePlayground │ │ │ │ └── usePlayground.ts │ │ │ ├── useProjectApi │ │ │ │ └── useProjectApi.ts │ │ │ ├── useProjectRolesApi │ │ │ │ └── useProjectRolesApi.ts │ │ │ ├── useSegmentsApi │ │ │ │ └── useSegmentsApi.ts │ │ │ ├── useSplashApi │ │ │ │ └── useSplashApi.ts │ │ │ ├── useStrategiesApi │ │ │ │ └── useStrategiesApi.ts │ │ │ ├── useTagTypesApi │ │ │ │ └── useTagTypesApi.ts │ │ │ └── useUiConfigApi │ │ │ │ └── useUiConfigApi.ts │ │ └── getters │ │ │ ├── httpErrorResponseHandler.ts │ │ │ ├── useAccess │ │ │ └── useAccess.ts │ │ │ ├── useAddons │ │ │ └── useAddons.ts │ │ │ ├── useApiGetter │ │ │ └── useApiGetter.ts │ │ │ ├── useApiTokens │ │ │ └── useApiTokens.ts │ │ │ ├── useApplication │ │ │ └── useApplication.ts │ │ │ ├── useApplications │ │ │ └── useApplications.ts │ │ │ ├── useAuth │ │ │ ├── useAuthDetails.ts │ │ │ ├── useAuthEndpoint.ts │ │ │ ├── useAuthFeedback.ts │ │ │ ├── useAuthPermissions.ts │ │ │ ├── useAuthSplash.ts │ │ │ └── useAuthUser.ts │ │ │ ├── useAuthSettings │ │ │ └── useAuthSettings.ts │ │ │ ├── useConstraintsValidation │ │ │ └── useConstraintsValidation.ts │ │ │ ├── useContext │ │ │ └── useContext.ts │ │ │ ├── useDefaultProject │ │ │ └── useDefaultProjectId.ts │ │ │ ├── useEnvironment │ │ │ ├── defaultEnvironment.ts │ │ │ └── useEnvironment.ts │ │ │ ├── useEnvironments │ │ │ └── useEnvironments.ts │ │ │ ├── useEventSearch │ │ │ └── useEventSearch.ts │ │ │ ├── useFeature │ │ │ ├── emptyFeature.ts │ │ │ ├── useFeature.ts │ │ │ └── useFeatureImmutable.ts │ │ │ ├── useFeatureMetrics │ │ │ └── useFeatureMetrics.ts │ │ │ ├── useFeatureMetricsRaw │ │ │ └── useFeatureMetricsRaw.ts │ │ │ ├── useFeatureTypes │ │ │ └── useFeatureTypes.ts │ │ │ ├── useFeatures │ │ │ └── useFeatures.ts │ │ │ ├── useFeaturesArchive │ │ │ └── useFeaturesArchive.ts │ │ │ ├── useGroup │ │ │ └── useGroup.ts │ │ │ ├── useGroups │ │ │ └── useGroups.ts │ │ │ ├── useHealthReport │ │ │ └── useHealthReport.ts │ │ │ ├── useInstanceStatus │ │ │ └── useInstanceStatus.ts │ │ │ ├── useInvoices │ │ │ └── useInvoices.ts │ │ │ ├── useProject │ │ │ ├── getProjectFetcher.ts │ │ │ └── useProject.ts │ │ │ ├── useProjectAccess │ │ │ └── useProjectAccess.ts │ │ │ ├── useProjectFeaturesArchive │ │ │ └── useProjectFeaturesArchive.ts │ │ │ ├── useProjectRole │ │ │ └── useProjectRole.ts │ │ │ ├── useProjectRolePermissions │ │ │ └── useProjectRolePermissions.ts │ │ │ ├── useProjectRoles │ │ │ └── useProjectRoles.ts │ │ │ ├── useProjects │ │ │ └── useProjects.ts │ │ │ ├── useResetPassword │ │ │ └── useResetPassword.ts │ │ │ ├── useSegment │ │ │ └── useSegment.ts │ │ │ ├── useSegmentLimits │ │ │ └── useSegmentLimits.ts │ │ │ ├── useSegmentValidation │ │ │ └── useSegmentValidation.ts │ │ │ ├── useSegments │ │ │ └── useSegments.ts │ │ │ ├── useStrategies │ │ │ └── useStrategies.ts │ │ │ ├── useStrategiesBySegment │ │ │ └── useStrategiesBySegment.ts │ │ │ ├── useStrategy │ │ │ └── useStrategy.ts │ │ │ ├── useTagType │ │ │ └── useTagType.ts │ │ │ ├── useTagTypes │ │ │ └── useTagTypes.ts │ │ │ ├── useTags │ │ │ └── useTags.ts │ │ │ ├── useUiConfig │ │ │ ├── defaultValue.ts │ │ │ └── useUiConfig.ts │ │ │ ├── useUnleashContext │ │ │ └── useUnleashContext.ts │ │ │ ├── useUserInfo │ │ │ └── useUserInfo.ts │ │ │ └── useUsers │ │ │ └── useUsers.ts │ ├── useDragItem.ts │ ├── useEventSettings.ts │ ├── useFeaturesFilter.test.ts │ ├── useFeaturesFilter.ts │ ├── useFormErrors.ts │ ├── useGlobalState.ts │ ├── useHiddenColumns.ts │ ├── useId.test.ts │ ├── useId.ts │ ├── useIsAppleDevice.ts │ ├── useKeyboardShortcut.ts │ ├── useLoading.ts │ ├── useLocationSettings.ts │ ├── useOnVisible.ts │ ├── useOptionalPathParam.ts │ ├── usePageTitle.ts │ ├── usePagination.ts │ ├── usePersistentGlobalState.ts │ ├── usePlausibleTracker.test.ts │ ├── usePlausibleTracker.ts │ ├── useQueryParams.ts │ ├── useQueryStringNumberState.ts │ ├── useQueryStringState.ts │ ├── useRequiredPathParam.ts │ ├── useRequiredQueryParam.ts │ ├── useSearch.test.ts │ ├── useSearch.ts │ ├── useTabs.ts │ ├── useThemeMode.ts │ ├── useToast.tsx │ ├── useUsersPlan.ts │ ├── useVirtualizedRange.ts │ └── useWeakMap.ts ├── index.tsx ├── interfaces │ ├── addons.ts │ ├── application.ts │ ├── context.ts │ ├── environments.ts │ ├── event.ts │ ├── featureToggle.ts │ ├── featureTypes.ts │ ├── group.ts │ ├── instance.ts │ ├── invoice.ts │ ├── project.ts │ ├── role.ts │ ├── route.ts │ ├── segment.ts │ ├── strategy.ts │ ├── tags.ts │ ├── toast.ts │ ├── token.ts │ ├── uiConfig.ts │ └── user.ts ├── openapi │ ├── apis │ │ ├── AdminApi.ts │ │ └── index.ts │ ├── index.ts │ ├── models │ │ ├── CloneFeatureSchema.ts │ │ ├── ConstraintSchema.ts │ │ ├── CreateFeatureSchema.ts │ │ ├── CreateStrategySchema.ts │ │ ├── FeatureEnvironmentSchema.ts │ │ ├── FeatureSchema.ts │ │ ├── FeatureStrategySchema.ts │ │ ├── FeaturesSchema.ts │ │ ├── OverrideSchema.ts │ │ ├── PatchOperationSchema.ts │ │ ├── StrategySchema.ts │ │ ├── TagSchema.ts │ │ ├── TagsResponseSchema.ts │ │ ├── UpdateFeatureSchema.ts │ │ ├── UpdateStrategySchema.ts │ │ ├── VariantSchema.ts │ │ ├── VariantSchemaPayload.ts │ │ └── index.ts │ └── runtime.ts ├── setupTests.ts ├── themes │ ├── ThemeProvider.tsx │ ├── app.css │ ├── colors.ts │ ├── dark-theme.ts │ ├── theme.ts │ ├── themeStyles.ts │ └── themeTypes.ts ├── types │ ├── global-fetch.d.ts │ └── react-table-config.d.ts ├── utils │ ├── apiUtils.ts │ ├── arraysHaveSameItems.test.ts │ ├── arraysHaveSameItems.ts │ ├── calculatePercentage.ts │ ├── cleanConstraint.test.ts │ ├── cleanConstraint.ts │ ├── createFeatureStrategy.test.ts │ ├── createFeatureStrategy.ts │ ├── createLocalStorage.ts │ ├── env.test.ts │ ├── env.ts │ ├── formatAccessText.test.ts │ ├── formatAccessText.ts │ ├── formatConstraintValue.ts │ ├── formatDate.ts │ ├── formatPath.test.ts │ ├── formatPath.ts │ ├── formatUnknownError.test.ts │ ├── formatUnknownError.ts │ ├── getFeatureMetrics.ts │ ├── getFeatureTypeIcons.ts │ ├── instanceTrial.test.ts │ ├── instanceTrial.ts │ ├── nonEmptyArray.ts │ ├── objectId.test.ts │ ├── objectId.ts │ ├── oneOf.ts │ ├── openapiClient.ts │ ├── operatorsForContext.ts │ ├── paginate.test.ts │ ├── paginate.ts │ ├── parseParameter.test.ts │ ├── parseParameter.ts │ ├── projectFilterGenerator.ts │ ├── removeEmptyStringFields.test.ts │ ├── removeEmptyStringFields.ts │ ├── routePathHelpers.ts │ ├── sortStrategyParameters.test.ts │ ├── sortStrategyParameters.ts │ ├── sortTypes.ts │ ├── storage.ts │ ├── strategyNames.tsx │ ├── testIds.ts │ ├── testRenderer.tsx │ ├── testServer.ts │ ├── validateParameterValue.test.ts │ └── validateParameterValue.ts └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json ├── vite.config.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = space 8 | indent_size = 4 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [*.json] 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.github/workflows/add-to-project.yml: -------------------------------------------------------------------------------- 1 | name: Add new item to project board 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | pull_request_target: 8 | types: 9 | - opened 10 | 11 | jobs: 12 | add-to-project: 13 | uses: unleash/.github/.github/workflows/add-item-to-project.yml@main 14 | secrets: inherit 15 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [14.x] 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@v3 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | - run: yarn install --frozen-lockfile 22 | - run: yarn run test 23 | - run: yarn run fmt:check 24 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 14.20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/* 2 | /src/openapi 3 | CHANGELOG.md 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "bracketSpacing": true, 4 | "bracketSameLine": false, 5 | "arrowParens": "avoid", 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "tc2qff", 3 | "defaultCommandTimeout": 12000, 4 | "screenshotOnRunFailure": false, 5 | "video": false, 6 | "experimentalSessionAndOrigin": true 7 | } 8 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('pkginfo')(module, 'version'); 2 | const path = require('path'); 3 | 4 | const { version } = module.exports; 5 | 6 | module.exports = { 7 | publicFolder: path.join(__dirname, 'build'), 8 | version 9 | }; 10 | -------------------------------------------------------------------------------- /public/cs_CZ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/cs_CZ.png -------------------------------------------------------------------------------- /public/da-DK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/da-DK.png -------------------------------------------------------------------------------- /public/de_DE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/de_DE.png -------------------------------------------------------------------------------- /public/en-GB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/en-GB.png -------------------------------------------------------------------------------- /public/en-IN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/en-IN.png -------------------------------------------------------------------------------- /public/en-US.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/en-US.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon_old.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/favicon_old.ico -------------------------------------------------------------------------------- /public/flags-normal/ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ad.png -------------------------------------------------------------------------------- /public/flags-normal/ae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ae.png -------------------------------------------------------------------------------- /public/flags-normal/af.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/af.png -------------------------------------------------------------------------------- /public/flags-normal/ag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ag.png -------------------------------------------------------------------------------- /public/flags-normal/al.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/al.png -------------------------------------------------------------------------------- /public/flags-normal/am.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/am.png -------------------------------------------------------------------------------- /public/flags-normal/ao.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ao.png -------------------------------------------------------------------------------- /public/flags-normal/ar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ar.png -------------------------------------------------------------------------------- /public/flags-normal/at.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/at.png -------------------------------------------------------------------------------- /public/flags-normal/au.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/au.png -------------------------------------------------------------------------------- /public/flags-normal/az.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/az.png -------------------------------------------------------------------------------- /public/flags-normal/ba.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ba.png -------------------------------------------------------------------------------- /public/flags-normal/bb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bb.png -------------------------------------------------------------------------------- /public/flags-normal/bd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bd.png -------------------------------------------------------------------------------- /public/flags-normal/be.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/be.png -------------------------------------------------------------------------------- /public/flags-normal/bf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bf.png -------------------------------------------------------------------------------- /public/flags-normal/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bg.png -------------------------------------------------------------------------------- /public/flags-normal/bh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bh.png -------------------------------------------------------------------------------- /public/flags-normal/bi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bi.png -------------------------------------------------------------------------------- /public/flags-normal/bj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bj.png -------------------------------------------------------------------------------- /public/flags-normal/bn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bn.png -------------------------------------------------------------------------------- /public/flags-normal/bo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bo.png -------------------------------------------------------------------------------- /public/flags-normal/br.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/br.png -------------------------------------------------------------------------------- /public/flags-normal/bs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bs.png -------------------------------------------------------------------------------- /public/flags-normal/bt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bt.png -------------------------------------------------------------------------------- /public/flags-normal/bw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bw.png -------------------------------------------------------------------------------- /public/flags-normal/by.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/by.png -------------------------------------------------------------------------------- /public/flags-normal/bz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/bz.png -------------------------------------------------------------------------------- /public/flags-normal/ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ca.png -------------------------------------------------------------------------------- /public/flags-normal/cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cd.png -------------------------------------------------------------------------------- /public/flags-normal/cf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cf.png -------------------------------------------------------------------------------- /public/flags-normal/cg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cg.png -------------------------------------------------------------------------------- /public/flags-normal/ch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ch.png -------------------------------------------------------------------------------- /public/flags-normal/ci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ci.png -------------------------------------------------------------------------------- /public/flags-normal/cl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cl.png -------------------------------------------------------------------------------- /public/flags-normal/cm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cm.png -------------------------------------------------------------------------------- /public/flags-normal/cn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cn.png -------------------------------------------------------------------------------- /public/flags-normal/co.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/co.png -------------------------------------------------------------------------------- /public/flags-normal/cr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cr.png -------------------------------------------------------------------------------- /public/flags-normal/cu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cu.png -------------------------------------------------------------------------------- /public/flags-normal/cv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cv.png -------------------------------------------------------------------------------- /public/flags-normal/cy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cy.png -------------------------------------------------------------------------------- /public/flags-normal/cz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/cz.png -------------------------------------------------------------------------------- /public/flags-normal/de.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/de.png -------------------------------------------------------------------------------- /public/flags-normal/dj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/dj.png -------------------------------------------------------------------------------- /public/flags-normal/dk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/dk.png -------------------------------------------------------------------------------- /public/flags-normal/dm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/dm.png -------------------------------------------------------------------------------- /public/flags-normal/do.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/do.png -------------------------------------------------------------------------------- /public/flags-normal/dz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/dz.png -------------------------------------------------------------------------------- /public/flags-normal/ec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ec.png -------------------------------------------------------------------------------- /public/flags-normal/ee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ee.png -------------------------------------------------------------------------------- /public/flags-normal/eg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/eg.png -------------------------------------------------------------------------------- /public/flags-normal/eh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/eh.png -------------------------------------------------------------------------------- /public/flags-normal/er.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/er.png -------------------------------------------------------------------------------- /public/flags-normal/es.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/es.png -------------------------------------------------------------------------------- /public/flags-normal/et.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/et.png -------------------------------------------------------------------------------- /public/flags-normal/fi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/fi.png -------------------------------------------------------------------------------- /public/flags-normal/fj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/fj.png -------------------------------------------------------------------------------- /public/flags-normal/fm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/fm.png -------------------------------------------------------------------------------- /public/flags-normal/fr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/fr.png -------------------------------------------------------------------------------- /public/flags-normal/ga.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ga.png -------------------------------------------------------------------------------- /public/flags-normal/gb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gb.png -------------------------------------------------------------------------------- /public/flags-normal/gd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gd.png -------------------------------------------------------------------------------- /public/flags-normal/ge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ge.png -------------------------------------------------------------------------------- /public/flags-normal/gh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gh.png -------------------------------------------------------------------------------- /public/flags-normal/gm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gm.png -------------------------------------------------------------------------------- /public/flags-normal/gn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gn.png -------------------------------------------------------------------------------- /public/flags-normal/gq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gq.png -------------------------------------------------------------------------------- /public/flags-normal/gr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gr.png -------------------------------------------------------------------------------- /public/flags-normal/gt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gt.png -------------------------------------------------------------------------------- /public/flags-normal/gw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gw.png -------------------------------------------------------------------------------- /public/flags-normal/gy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/gy.png -------------------------------------------------------------------------------- /public/flags-normal/hn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/hn.png -------------------------------------------------------------------------------- /public/flags-normal/hr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/hr.png -------------------------------------------------------------------------------- /public/flags-normal/ht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ht.png -------------------------------------------------------------------------------- /public/flags-normal/hu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/hu.png -------------------------------------------------------------------------------- /public/flags-normal/id.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/id.png -------------------------------------------------------------------------------- /public/flags-normal/ie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ie.png -------------------------------------------------------------------------------- /public/flags-normal/il.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/il.png -------------------------------------------------------------------------------- /public/flags-normal/in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/in.png -------------------------------------------------------------------------------- /public/flags-normal/iq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/iq.png -------------------------------------------------------------------------------- /public/flags-normal/ir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ir.png -------------------------------------------------------------------------------- /public/flags-normal/is.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/is.png -------------------------------------------------------------------------------- /public/flags-normal/it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/it.png -------------------------------------------------------------------------------- /public/flags-normal/jm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/jm.png -------------------------------------------------------------------------------- /public/flags-normal/jo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/jo.png -------------------------------------------------------------------------------- /public/flags-normal/jp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/jp.png -------------------------------------------------------------------------------- /public/flags-normal/ke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ke.png -------------------------------------------------------------------------------- /public/flags-normal/kg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kg.png -------------------------------------------------------------------------------- /public/flags-normal/kh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kh.png -------------------------------------------------------------------------------- /public/flags-normal/ki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ki.png -------------------------------------------------------------------------------- /public/flags-normal/km.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/km.png -------------------------------------------------------------------------------- /public/flags-normal/kn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kn.png -------------------------------------------------------------------------------- /public/flags-normal/kp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kp.png -------------------------------------------------------------------------------- /public/flags-normal/kr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kr.png -------------------------------------------------------------------------------- /public/flags-normal/ks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ks.png -------------------------------------------------------------------------------- /public/flags-normal/kw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kw.png -------------------------------------------------------------------------------- /public/flags-normal/kz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/kz.png -------------------------------------------------------------------------------- /public/flags-normal/la.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/la.png -------------------------------------------------------------------------------- /public/flags-normal/lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lb.png -------------------------------------------------------------------------------- /public/flags-normal/lc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lc.png -------------------------------------------------------------------------------- /public/flags-normal/li.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/li.png -------------------------------------------------------------------------------- /public/flags-normal/lk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lk.png -------------------------------------------------------------------------------- /public/flags-normal/lr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lr.png -------------------------------------------------------------------------------- /public/flags-normal/ls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ls.png -------------------------------------------------------------------------------- /public/flags-normal/lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lt.png -------------------------------------------------------------------------------- /public/flags-normal/lu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lu.png -------------------------------------------------------------------------------- /public/flags-normal/lv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/lv.png -------------------------------------------------------------------------------- /public/flags-normal/ly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ly.png -------------------------------------------------------------------------------- /public/flags-normal/ma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ma.png -------------------------------------------------------------------------------- /public/flags-normal/mc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mc.png -------------------------------------------------------------------------------- /public/flags-normal/md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/md.png -------------------------------------------------------------------------------- /public/flags-normal/me.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/me.png -------------------------------------------------------------------------------- /public/flags-normal/mg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mg.png -------------------------------------------------------------------------------- /public/flags-normal/mh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mh.png -------------------------------------------------------------------------------- /public/flags-normal/mk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mk.png -------------------------------------------------------------------------------- /public/flags-normal/ml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ml.png -------------------------------------------------------------------------------- /public/flags-normal/mm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mm.png -------------------------------------------------------------------------------- /public/flags-normal/mn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mn.png -------------------------------------------------------------------------------- /public/flags-normal/mr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mr.png -------------------------------------------------------------------------------- /public/flags-normal/mt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mt.png -------------------------------------------------------------------------------- /public/flags-normal/mu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mu.png -------------------------------------------------------------------------------- /public/flags-normal/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mv.png -------------------------------------------------------------------------------- /public/flags-normal/mw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mw.png -------------------------------------------------------------------------------- /public/flags-normal/mx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mx.png -------------------------------------------------------------------------------- /public/flags-normal/my.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/my.png -------------------------------------------------------------------------------- /public/flags-normal/mz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/mz.png -------------------------------------------------------------------------------- /public/flags-normal/na.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/na.png -------------------------------------------------------------------------------- /public/flags-normal/ne.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ne.png -------------------------------------------------------------------------------- /public/flags-normal/ng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ng.png -------------------------------------------------------------------------------- /public/flags-normal/ni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ni.png -------------------------------------------------------------------------------- /public/flags-normal/nl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/nl.png -------------------------------------------------------------------------------- /public/flags-normal/no.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/no.png -------------------------------------------------------------------------------- /public/flags-normal/np.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/np.png -------------------------------------------------------------------------------- /public/flags-normal/nr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/nr.png -------------------------------------------------------------------------------- /public/flags-normal/nz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/nz.png -------------------------------------------------------------------------------- /public/flags-normal/om.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/om.png -------------------------------------------------------------------------------- /public/flags-normal/pa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pa.png -------------------------------------------------------------------------------- /public/flags-normal/pe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pe.png -------------------------------------------------------------------------------- /public/flags-normal/pg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pg.png -------------------------------------------------------------------------------- /public/flags-normal/ph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ph.png -------------------------------------------------------------------------------- /public/flags-normal/pk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pk.png -------------------------------------------------------------------------------- /public/flags-normal/pl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pl.png -------------------------------------------------------------------------------- /public/flags-normal/pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pt.png -------------------------------------------------------------------------------- /public/flags-normal/pw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/pw.png -------------------------------------------------------------------------------- /public/flags-normal/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/py.png -------------------------------------------------------------------------------- /public/flags-normal/qa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/qa.png -------------------------------------------------------------------------------- /public/flags-normal/ro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ro.png -------------------------------------------------------------------------------- /public/flags-normal/rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/rs.png -------------------------------------------------------------------------------- /public/flags-normal/ru.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ru.png -------------------------------------------------------------------------------- /public/flags-normal/rw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/rw.png -------------------------------------------------------------------------------- /public/flags-normal/sa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sa.png -------------------------------------------------------------------------------- /public/flags-normal/sb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sb.png -------------------------------------------------------------------------------- /public/flags-normal/sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sc.png -------------------------------------------------------------------------------- /public/flags-normal/sd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sd.png -------------------------------------------------------------------------------- /public/flags-normal/se.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/se.png -------------------------------------------------------------------------------- /public/flags-normal/sg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sg.png -------------------------------------------------------------------------------- /public/flags-normal/si.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/si.png -------------------------------------------------------------------------------- /public/flags-normal/sk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sk.png -------------------------------------------------------------------------------- /public/flags-normal/sl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sl.png -------------------------------------------------------------------------------- /public/flags-normal/sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sm.png -------------------------------------------------------------------------------- /public/flags-normal/sn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sn.png -------------------------------------------------------------------------------- /public/flags-normal/so.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/so.png -------------------------------------------------------------------------------- /public/flags-normal/sr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sr.png -------------------------------------------------------------------------------- /public/flags-normal/st.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/st.png -------------------------------------------------------------------------------- /public/flags-normal/sv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sv.png -------------------------------------------------------------------------------- /public/flags-normal/sy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sy.png -------------------------------------------------------------------------------- /public/flags-normal/sz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/sz.png -------------------------------------------------------------------------------- /public/flags-normal/td.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/td.png -------------------------------------------------------------------------------- /public/flags-normal/tg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tg.png -------------------------------------------------------------------------------- /public/flags-normal/th.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/th.png -------------------------------------------------------------------------------- /public/flags-normal/tj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tj.png -------------------------------------------------------------------------------- /public/flags-normal/tl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tl.png -------------------------------------------------------------------------------- /public/flags-normal/tm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tm.png -------------------------------------------------------------------------------- /public/flags-normal/tn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tn.png -------------------------------------------------------------------------------- /public/flags-normal/to.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/to.png -------------------------------------------------------------------------------- /public/flags-normal/tr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tr.png -------------------------------------------------------------------------------- /public/flags-normal/tt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tt.png -------------------------------------------------------------------------------- /public/flags-normal/tv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tv.png -------------------------------------------------------------------------------- /public/flags-normal/tw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tw.png -------------------------------------------------------------------------------- /public/flags-normal/tz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/tz.png -------------------------------------------------------------------------------- /public/flags-normal/ua.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ua.png -------------------------------------------------------------------------------- /public/flags-normal/ug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ug.png -------------------------------------------------------------------------------- /public/flags-normal/us.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/us.png -------------------------------------------------------------------------------- /public/flags-normal/uy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/uy.png -------------------------------------------------------------------------------- /public/flags-normal/uz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/uz.png -------------------------------------------------------------------------------- /public/flags-normal/va.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/va.png -------------------------------------------------------------------------------- /public/flags-normal/vc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/vc.png -------------------------------------------------------------------------------- /public/flags-normal/ve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ve.png -------------------------------------------------------------------------------- /public/flags-normal/vn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/vn.png -------------------------------------------------------------------------------- /public/flags-normal/vu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/vu.png -------------------------------------------------------------------------------- /public/flags-normal/ws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ws.png -------------------------------------------------------------------------------- /public/flags-normal/ye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/ye.png -------------------------------------------------------------------------------- /public/flags-normal/za.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/za.png -------------------------------------------------------------------------------- /public/flags-normal/zm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/zm.png -------------------------------------------------------------------------------- /public/flags-normal/zw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/flags-normal/zw.png -------------------------------------------------------------------------------- /public/fr-FR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/fr-FR.png -------------------------------------------------------------------------------- /public/logo-filled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/logo-filled.png -------------------------------------------------------------------------------- /public/logo-inverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/logo-inverted.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/logo.png -------------------------------------------------------------------------------- /public/logo_old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/logo_old.png -------------------------------------------------------------------------------- /public/nb-NO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/nb-NO.png -------------------------------------------------------------------------------- /public/pt_BR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/pt_BR.png -------------------------------------------------------------------------------- /public/sv-SE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/sv-SE.png -------------------------------------------------------------------------------- /public/unknown-locale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/public/unknown-locale.png -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "packageRules": [ 4 | { 5 | "matchUpdateTypes": ["minor", "patch", "pin", "digest"], 6 | "matchPackagePatterns": ["*"], 7 | "automerge": true 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /scripts/generate-openapi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Generate OpenAPI bindings for the Unleash API. 4 | # https://openapi-generator.tech/docs/generators/typescript-fetch 5 | 6 | set -feux 7 | cd "$(dirname "$0")" 8 | 9 | # URL to the generated open API spec. 10 | # Set the UNLEASH_OPENAPI_URL environment variable to override. 11 | UNLEASH_OPENAPI_URL="${UNLEASH_OPENAPI_URL:-http://localhost:4242/docs/openapi.json}" 12 | 13 | rm -rf "../src/openapi" 14 | mkdir "../src/openapi" 15 | 16 | npx @openapitools/openapi-generator-cli generate \ 17 | -g "typescript-fetch" \ 18 | -i "$UNLEASH_OPENAPI_URL" \ 19 | -o "../src/openapi" 20 | 21 | # Remove unused files. 22 | rm "openapitools.json" 23 | rm "../src/openapi/.openapi-generator-ignore" 24 | rm -r "../src/openapi/.openapi-generator" 25 | -------------------------------------------------------------------------------- /src/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /src/__mocks__/svgMock.js: -------------------------------------------------------------------------------- 1 | export default 'SvgrURL'; 2 | export const ReactComponent = 'div'; 3 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto300.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/roboto300.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/roboto400.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto500.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/roboto500.ttf -------------------------------------------------------------------------------- /src/assets/fonts/roboto700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/roboto700.ttf -------------------------------------------------------------------------------- /src/assets/fonts/senBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/senBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/senExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/senExtraBold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/senRegular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/fonts/senRegular.ttf -------------------------------------------------------------------------------- /src/assets/icons/24_Negator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/24_Text format.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/dots.svg: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/logoBg.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/logoInverted.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/logoPlain.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/logoWhiteBg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/projectIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icons/rollout.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/star.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/unknownUser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/icons/unknownUser.png -------------------------------------------------------------------------------- /src/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/img/logoDark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/img/mobileGuidanceBg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/img/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/img/texture.png -------------------------------------------------------------------------------- /src/assets/img/unleashLogoIconDarkAlpha.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Unleash/unleash-frontend/3ede954bde24258a0e7ae85f894eb676fa516fa6/src/assets/img/unleashLogoIconDarkAlpha.gif -------------------------------------------------------------------------------- /src/component/addons/AddonList/AddonList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ConfiguredAddons } from './ConfiguredAddons/ConfiguredAddons'; 3 | import { AvailableAddons } from './AvailableAddons/AvailableAddons'; 4 | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; 5 | import useAddons from 'hooks/api/getters/useAddons/useAddons'; 6 | 7 | export const AddonList = () => { 8 | const { providers, addons, loading } = useAddons(); 9 | 10 | return ( 11 | <> 12 | 0} 14 | show={} 15 | /> 16 | 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/component/addons/AddonList/AvailableAddons/ConfigureAddonButton/ConfigureAddonButton.tsx: -------------------------------------------------------------------------------- 1 | import PermissionButton from 'component/common/PermissionButton/PermissionButton'; 2 | import { CREATE_ADDON } from 'component/providers/AccessProvider/permissions'; 3 | import { useNavigate } from 'react-router-dom'; 4 | 5 | interface IConfigureAddonButtonProps { 6 | name: string; 7 | } 8 | 9 | export const ConfigureAddonButton = ({ name }: IConfigureAddonButtonProps) => { 10 | const navigate = useNavigate(); 11 | 12 | return ( 13 | navigate(`/addons/create/${name}`)} 17 | > 18 | Configure 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/admin/api/index.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router-dom'; 2 | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; 3 | import { ApiTokenPage } from 'component/admin/apiToken/ApiTokenPage/ApiTokenPage'; 4 | import AdminMenu from '../menu/AdminMenu'; 5 | 6 | const ApiPage = () => { 7 | const { pathname } = useLocation(); 8 | const showAdminMenu = pathname.includes('/admin/'); 9 | 10 | return ( 11 |
12 | } 15 | /> 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default ApiPage; 22 | -------------------------------------------------------------------------------- /src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectAllButton/SelectAllButton.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | selectOptionsLink: { 5 | cursor: 'pointer', 6 | fontSize: theme.fontSizes.bodySize, 7 | }, 8 | })); 9 | -------------------------------------------------------------------------------- /src/component/admin/apiToken/ApiTokenForm/SelectProjectInput/SelectProjectInput.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | selectOptionCheckbox: { 5 | marginRight: '0.2rem', 6 | }, 7 | })); 8 | -------------------------------------------------------------------------------- /src/component/admin/billing/BillingDashboard/BillingDashboard.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from '@mui/material'; 2 | import { IInstanceStatus } from 'interfaces/instance'; 3 | import { VFC } from 'react'; 4 | import { BillingInformation } from './BillingInformation/BillingInformation'; 5 | import { BillingPlan } from './BillingPlan/BillingPlan'; 6 | 7 | interface IBillingDashboardProps { 8 | instanceStatus: IInstanceStatus; 9 | } 10 | 11 | export const BillingDashboard: VFC = ({ 12 | instanceStatus, 13 | }) => { 14 | return ( 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/component/admin/billing/BillingDashboard/BillingPlan/GridColLink/GridColLink.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material'; 2 | import { FC } from 'react'; 3 | 4 | const StyledSpan = styled('span')(({ theme }) => ({ 5 | fontSize: theme.fontSizes.smallBody, 6 | marginLeft: theme.spacing(1), 7 | })); 8 | 9 | export const GridColLink: FC = ({ children }) => { 10 | return ({children}); 11 | }; 12 | -------------------------------------------------------------------------------- /src/component/admin/billing/FlaggedBillingRedirect/FlaggedBillingRedirect.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate } from 'react-router-dom'; 2 | import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; 3 | import InvoiceAdminPage from 'component/admin/invoice/InvoiceAdminPage'; 4 | 5 | const FlaggedBillingRedirect = () => { 6 | const { uiConfig, loading } = useUiConfig(); 7 | 8 | if (loading) { 9 | return null; 10 | } 11 | 12 | if (!uiConfig.flags.UNLEASH_CLOUD) { 13 | return ; 14 | } 15 | 16 | return ; 17 | }; 18 | 19 | export default FlaggedBillingRedirect; 20 | -------------------------------------------------------------------------------- /src/component/admin/billing/flags.ts: -------------------------------------------------------------------------------- 1 | export const STRIPE = false; 2 | -------------------------------------------------------------------------------- /src/component/admin/cors/CorsHelpAlert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from '@mui/material'; 3 | 4 | export const CorsHelpAlert = () => { 5 | return ( 6 | 7 |

8 | Use this page to configure allowed CORS origins for the Frontend 9 | API (/api/frontend). 10 |

11 |

12 | This configuration will not affect the Admin API ( 13 | /api/admin) nor the Client API ( 14 | /api/client). 15 |

16 |

17 | An asterisk (*) may be used to allow API calls from 18 | any origin. 19 |

20 |
21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/component/admin/cors/CorsTokenAlert.tsx: -------------------------------------------------------------------------------- 1 | import { TokenType } from 'interfaces/token'; 2 | import { Link } from 'react-router-dom'; 3 | import { Alert } from '@mui/material'; 4 | 5 | export const CorsTokenAlert = () => { 6 | return ( 7 | 8 | By default, all {TokenType.FRONTEND} tokens may be used from any 9 | CORS origin. If you'd like to configure a strict set of origins, 10 | please use the{' '} 11 | 12 | CORS origins configuration page 13 | 14 | . 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/component/admin/groups/GroupsAdmin.tsx: -------------------------------------------------------------------------------- 1 | import { GroupsList } from './GroupsList/GroupsList'; 2 | import AdminMenu from '../menu/AdminMenu'; 3 | 4 | export const GroupsAdmin = () => { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/component/admin/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Navigate } from 'react-router-dom'; 4 | 5 | const render = () => ; 6 | 7 | render.propTypes = { 8 | match: PropTypes.object.isRequired, 9 | history: PropTypes.object.isRequired, 10 | }; 11 | 12 | export default render; 13 | -------------------------------------------------------------------------------- /src/component/admin/projectRoles/ProjectRoles/ProjectRoleDeleteConfirm/ProjectRoleDeleteConfirm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | deleteParagraph: { 5 | marginTop: '2rem', 6 | }, 7 | roleDeleteInput: { 8 | marginTop: '1rem', 9 | }, 10 | })); 11 | -------------------------------------------------------------------------------- /src/component/admin/projectRoles/ProjectRoles/ProjectRoles.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | rolesListBody: { 5 | padding: theme.spacing(4), 6 | paddingBottom: '4rem', 7 | minHeight: '50vh', 8 | position: 'relative', 9 | }, 10 | })); 11 | -------------------------------------------------------------------------------- /src/component/admin/users/ConfirmUserAdded/ConfirmUserEmail/ConfirmUserEmail.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()({ 4 | iconContainer: { 5 | width: '100%', 6 | textAlign: 'center', 7 | }, 8 | emailIcon: { 9 | margin: '2rem auto', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /src/component/admin/users/util.ts: -------------------------------------------------------------------------------- 1 | export const modalStyles = { 2 | overlay: { 3 | position: 'absolute', 4 | top: 0, 5 | left: 0, 6 | right: 0, 7 | bottom: 0, 8 | backgroundColor: 'rgba(0, 0, 0, 0.25)', 9 | zIndex: 5, 10 | }, 11 | content: { 12 | width: '500px', 13 | maxWidth: '90%', 14 | margin: '0', 15 | top: '50%', 16 | left: '50%', 17 | right: 'auto', 18 | bottom: 'auto', 19 | transform: 'translate(-50%, -50%)', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/archive/RedirectArchive.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate } from 'react-router-dom'; 2 | 3 | const RedirectArchive = () => { 4 | return ; 5 | }; 6 | 7 | export default RedirectArchive; 8 | -------------------------------------------------------------------------------- /src/component/common/AdminAlert/AdminAlert.tsx: -------------------------------------------------------------------------------- 1 | import { Alert } from '@mui/material'; 2 | 3 | export const AdminAlert = () => { 4 | return ( 5 | 6 | You need instance admin to access this section. 7 | 8 | ); 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/common/Announcer/AnnouncerContext/AnnouncerContext.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface IAnnouncerContext { 4 | setAnnouncement: React.Dispatch>; 5 | } 6 | 7 | const setAnnouncementPlaceholder = () => { 8 | throw new Error('setAnnouncement called outside AnnouncerContext'); 9 | }; 10 | 11 | // AnnouncerContext announces messages to screen readers through a live region. 12 | // Call setAnnouncement to broadcast a new message to the screen reader. 13 | export const AnnouncerContext = React.createContext({ 14 | setAnnouncement: setAnnouncementPlaceholder, 15 | }); 16 | -------------------------------------------------------------------------------- /src/component/common/Announcer/AnnouncerElement/AnnouncerElement.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()({ 4 | container: { 5 | clip: 'rect(0 0 0 0)', 6 | clipPath: 'inset(50%)', 7 | zIndex: -1, 8 | width: 1, 9 | height: 1, 10 | margin: -1, 11 | padding: 0, 12 | overflow: 'hidden', 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/component/common/BreadcrumbNav/BreadcrumbNav.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | breadcrumbNav: { 5 | position: 'absolute', 6 | top: '4px', 7 | }, 8 | breadcrumbNavParagraph: { 9 | color: 'inherit', 10 | '& > *': { 11 | verticalAlign: 'middle', 12 | }, 13 | }, 14 | breadcrumbLink: { 15 | textDecoration: 'none', 16 | '& > *': { 17 | verticalAlign: 'middle', 18 | }, 19 | color: theme.palette.primary.main, 20 | }, 21 | })); 22 | -------------------------------------------------------------------------------- /src/component/common/CheckmarkBadge/CheckMarkBadge.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | badge: { 5 | backgroundColor: theme.palette.checkmarkBadge, 6 | width: '75px', 7 | height: '75px', 8 | borderRadius: '50px', 9 | display: 'flex', 10 | justifyContent: 'center', 11 | alignItems: 'center', 12 | [theme.breakpoints.down('sm')]: { 13 | width: '50px', 14 | height: '50px', 15 | }, 16 | }, 17 | check: { 18 | color: '#fff', 19 | width: '35px', 20 | height: '35px', 21 | }, 22 | })); 23 | -------------------------------------------------------------------------------- /src/component/common/CheckmarkBadge/CheckMarkBadge.tsx: -------------------------------------------------------------------------------- 1 | import { Check, Close } from '@mui/icons-material'; 2 | import { useStyles } from './CheckMarkBadge.styles'; 3 | import classnames from 'classnames'; 4 | 5 | interface ICheckMarkBadgeProps { 6 | className: string; 7 | type?: string; 8 | } 9 | 10 | const CheckMarkBadge = ({ type, className }: ICheckMarkBadgeProps) => { 11 | const { classes: styles } = useStyles(); 12 | return ( 13 |
14 | {type === 'error' ? ( 15 | 16 | ) : ( 17 | 18 | )} 19 |
20 | ); 21 | }; 22 | 23 | export default CheckMarkBadge; 24 | -------------------------------------------------------------------------------- /src/component/common/Codebox/Codebox.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | backgroundColor: theme.palette.codebox, 6 | padding: '1rem', 7 | borderRadius: theme.shape.borderRadiusMedium, 8 | position: 'relative', 9 | maxHeight: '500px', 10 | overflow: 'auto', 11 | }, 12 | code: { 13 | margin: 0, 14 | wordBreak: 'break-all', 15 | whiteSpace: 'pre-wrap', 16 | color: theme.palette.formSidebarTextColor, 17 | fontSize: 14, 18 | }, 19 | icon: { 20 | fill: '#fff', 21 | }, 22 | iconButton: { 23 | position: 'absolute', 24 | bottom: '10px', 25 | right: '20px', 26 | }, 27 | })); 28 | -------------------------------------------------------------------------------- /src/component/common/Codebox/Codebox.tsx: -------------------------------------------------------------------------------- 1 | import { useStyles } from './Codebox.styles'; 2 | 3 | interface ICodeboxProps { 4 | text: string; 5 | } 6 | 7 | const Codebox = ({ text }: ICodeboxProps) => { 8 | const { classes: styles } = useStyles(); 9 | 10 | return ( 11 |
12 |
{text}
13 |
14 | ); 15 | }; 16 | 17 | export default Codebox; 18 | -------------------------------------------------------------------------------- /src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/ConstraintFormHeader/ConstraintFormHeader.tsx: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | import React from 'react'; 3 | 4 | const useStyles = makeStyles()(theme => ({ 5 | header: { 6 | fontSize: theme.fontSizes.bodySize, 7 | fontWeight: 'normal', 8 | marginTop: '1rem', 9 | marginBottom: '0.25rem', 10 | }, 11 | })); 12 | 13 | export const ConstraintFormHeader: React.FC< 14 | React.HTMLAttributes 15 | > = ({ children, ...rest }) => { 16 | const { classes: styles } = useStyles(); 17 | return ( 18 |

19 | {children} 20 |

21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/DateSingleValue/DateSingleValue.test.tsx: -------------------------------------------------------------------------------- 1 | import { parseDateValue } from 'component/common/util'; 2 | 3 | test(`Date component is able to parse midnight when it's 00`, () => { 4 | let f = parseDateValue('2022-03-15T12:27'); 5 | let midnight = parseDateValue('2022-03-15T00:27'); 6 | expect(f).toEqual('2022-03-15T12:27'); 7 | expect(midnight).toEqual('2022-03-15T00:27'); 8 | }); 9 | 10 | test(`Date component - snapshot matching`, () => { 11 | let midnight = '2022-03-15T00:00'; 12 | let midday = '2022-03-15T12:00'; 13 | let obj = { 14 | midnight: parseDateValue(midnight), 15 | midday: parseDateValue(midday), 16 | }; 17 | expect(obj).toMatchSnapshot(); 18 | }); 19 | -------------------------------------------------------------------------------- /src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/DateSingleValue/__snapshots__/DateSingleValue.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1 2 | 3 | exports[`Date component - snapshot matching 1`] = ` 4 | { 5 | "midday": "2022-03-15T12:00", 6 | "midnight": "2022-03-15T00:00", 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /src/component/common/ConstraintAccordion/ConstraintAccordionEdit/ConstraintAccordionEditBody/LegalValueLabel/LegalValueLabel.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'inline-block', 6 | wordBreak: 'break-word', 7 | }, 8 | value: { 9 | lineHeight: 1.33, 10 | fontSize: theme.fontSizes.smallBody, 11 | }, 12 | description: { 13 | lineHeight: 1.33, 14 | fontSize: theme.fontSizes.smallerBody, 15 | color: theme.palette.grey[700], 16 | }, 17 | })); 18 | -------------------------------------------------------------------------------- /src/component/common/ConstraintAccordion/ConstraintAccordionList/createEmptyConstraint.ts: -------------------------------------------------------------------------------- 1 | import { dateOperators } from 'constants/operators'; 2 | import { IConstraint } from 'interfaces/strategy'; 3 | import { oneOf } from 'utils/oneOf'; 4 | import { operatorsForContext } from 'utils/operatorsForContext'; 5 | 6 | export const createEmptyConstraint = (contextName: string): IConstraint => { 7 | const operator = operatorsForContext(contextName)[0]; 8 | 9 | const value = oneOf(dateOperators, operator) 10 | ? new Date().toISOString() 11 | : ''; 12 | 13 | return { 14 | contextName, 15 | operator, 16 | value, 17 | values: [], 18 | caseInsensitive: false, 19 | inverted: false, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/common/CreateButton/CreateButton.tsx: -------------------------------------------------------------------------------- 1 | import PermissionButton, { 2 | IPermissionButtonProps, 3 | } from '../PermissionButton/PermissionButton'; 4 | 5 | interface ICreateButtonProps extends IPermissionButtonProps { 6 | name: string; 7 | } 8 | 9 | export const CreateButton = ({ name, ...rest }: ICreateButtonProps) => { 10 | return ( 11 | 12 | Create {name} 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/component/common/Dialogue/Dialogue.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | dialogTitle: { 5 | backgroundColor: theme.palette.dialogHeaderBackground, 6 | color: theme.palette.dialogHeaderText, 7 | height: '150px', 8 | padding: '2rem 3rem', 9 | clipPath: ' ellipse(130% 115px at 120% 20%)', 10 | }, 11 | dialogContentPadding: { 12 | padding: '2rem 3rem', 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/common/DividerText/DividerText.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | alignItems: 'center', 7 | justifyContent: 'center', 8 | margin: '1rem auto', 9 | }, 10 | wing: { 11 | width: '80px', 12 | height: '3px', 13 | backgroundColor: theme.palette.divider, 14 | borderRadius: theme.shape.borderRadius, 15 | }, 16 | text: { 17 | textAlign: 'center', 18 | display: 'block', 19 | margin: '0 1rem', 20 | }, 21 | })); 22 | -------------------------------------------------------------------------------- /src/component/common/DividerText/DividerText.tsx: -------------------------------------------------------------------------------- 1 | import { Typography } from '@mui/material'; 2 | import { useStyles } from 'component/common/DividerText/DividerText.styles'; 3 | 4 | interface IDividerTextProps { 5 | text: string; 6 | } 7 | 8 | const DividerText = ({ text, ...rest }: IDividerTextProps) => { 9 | const { classes: styles } = useStyles(); 10 | 11 | return ( 12 |
13 | 14 | 15 | {text} 16 | 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default DividerText; 23 | -------------------------------------------------------------------------------- /src/component/common/DropdownMenu/DropdownButton/DropdownButton.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, VFC } from 'react'; 2 | import { Button, ButtonProps, Icon } from '@mui/material'; 3 | 4 | interface IDropdownButtonProps { 5 | label: string; 6 | id?: string; 7 | title?: ButtonProps['title']; 8 | className?: string; 9 | icon?: ReactNode; 10 | startIcon?: ButtonProps['startIcon']; 11 | style?: ButtonProps['style']; 12 | onClick: ButtonProps['onClick']; 13 | } 14 | 15 | export const DropdownButton: VFC = ({ 16 | label, 17 | icon, 18 | ...rest 19 | }) => ( 20 | 23 | ); 24 | -------------------------------------------------------------------------------- /src/component/common/EnvironmentStrategiesDialog/EnvironmentStrategyDialog.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | infoText: { 5 | marginBottom: '10px', 6 | fontSize: theme.fontSizes.bodySize, 7 | }, 8 | })); 9 | -------------------------------------------------------------------------------- /src/component/common/Gradient/Gradient.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface IGradientProps { 4 | from: string; 5 | to: string; 6 | style?: object; 7 | className?: string; 8 | } 9 | 10 | const Gradient: React.FC = ({ 11 | children, 12 | from, 13 | to, 14 | style, 15 | ...rest 16 | }) => { 17 | return ( 18 |
28 | {children} 29 |
30 | ); 31 | }; 32 | 33 | export default Gradient; 34 | -------------------------------------------------------------------------------- /src/component/common/GridCol/GridCol.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from '@mui/material'; 2 | import { FC } from 'react'; 3 | 4 | export const GridCol: FC<{ vertical?: boolean }> = ({ 5 | children, 6 | vertical = false, 7 | }) => { 8 | return ( 9 | 16 | {children} 17 | 18 | ); 19 | }; 20 | -------------------------------------------------------------------------------- /src/component/common/GridRow/GridRow.tsx: -------------------------------------------------------------------------------- 1 | import { Grid, styled, SxProps, Theme } from '@mui/material'; 2 | import { FC } from 'react'; 3 | 4 | const StyledGrid = styled(Grid)(({ theme }) => ({ 5 | flexWrap: 'nowrap', 6 | gap: theme.spacing(1), 7 | })); 8 | 9 | export const GridRow: FC<{ sx?: SxProps }> = ({ sx, children }) => { 10 | return ( 11 | 18 | {children} 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/common/HelpIcon/HelpIcon.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'inline-grid', 6 | alignItems: 'center', 7 | outline: 0, 8 | 9 | '&:is(:focus-visible, :active) > *, &:hover > *': { 10 | outlineStyle: 'solid', 11 | outlineWidth: 2, 12 | outlineOffset: 0, 13 | outlineColor: theme.palette.primary.main, 14 | borderRadius: '100%', 15 | color: theme.palette.primary.main, 16 | }, 17 | }, 18 | icon: { 19 | fontSize: '1rem', 20 | color: theme.palette.inactiveIcon, 21 | }, 22 | })); 23 | -------------------------------------------------------------------------------- /src/component/common/HelpIcon/HelpIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip, TooltipProps } from '@mui/material'; 2 | import { Info } from '@mui/icons-material'; 3 | import { useStyles } from 'component/common/HelpIcon/HelpIcon.styles'; 4 | import React from 'react'; 5 | 6 | interface IHelpIconProps { 7 | tooltip: string; 8 | placement?: TooltipProps['placement']; 9 | } 10 | 11 | export const HelpIcon = ({ tooltip, placement }: IHelpIconProps) => { 12 | const { classes: styles } = useStyles(); 13 | 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/common/Highlighter/Highlighter.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | highlighter: { 5 | '&>mark': { 6 | backgroundColor: theme.palette.highlight, 7 | }, 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/common/Input/Input.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | helperText: { 5 | position: 'absolute', 6 | top: '35px', 7 | }, 8 | inputContainer: { 9 | width: '100%', 10 | position: 'relative', 11 | }, 12 | })); 13 | -------------------------------------------------------------------------------- /src/component/common/InputCaption/InputCaption.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | 3 | export interface IInputCaptionProps { 4 | text?: string; 5 | } 6 | 7 | export const InputCaption = ({ text }: IInputCaptionProps) => { 8 | if (!text) { 9 | return null; 10 | } 11 | 12 | return ( 13 | ({ 15 | color: theme.palette.text.secondary, 16 | fontSize: theme.fontSizes.smallerBody, 17 | marginTop: theme.spacing(1), 18 | })} 19 | > 20 | {text} 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/component/common/Loader/Loader.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | loader: { 5 | display: 'flex', 6 | justifyContent: 'center', 7 | alignItems: 'center', 8 | height: '100%', 9 | backgroundColor: theme.palette.background.paper, 10 | }, 11 | img: { 12 | width: '100px', 13 | height: '100px', 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/common/Loader/Loader.tsx: -------------------------------------------------------------------------------- 1 | import logo from 'assets/img/unleashLogoIconDarkAlpha.gif'; 2 | import { formatAssetPath } from 'utils/formatPath'; 3 | import { useStyles } from './Loader.styles'; 4 | 5 | const Loader = () => { 6 | const { classes: styles } = useStyles(); 7 | 8 | return ( 9 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Loader; 16 | -------------------------------------------------------------------------------- /src/component/common/LoginRedirect/LoginRedirect.tsx: -------------------------------------------------------------------------------- 1 | import { useLocation, Navigate } from 'react-router-dom'; 2 | 3 | export const LoginRedirect = () => { 4 | const { pathname, search } = useLocation(); 5 | 6 | const redirect = encodeURIComponent(pathname + search); 7 | const loginLink = `/login?redirect=${redirect}`; 8 | 9 | return ; 10 | }; 11 | -------------------------------------------------------------------------------- /src/component/common/NoItems/NoItems.tsx: -------------------------------------------------------------------------------- 1 | import { ReactComponent as NoItemsIcon } from 'assets/icons/addfiles.svg'; 2 | import { useStyles } from './NoItems.styles'; 3 | import React from 'react'; 4 | 5 | const NoItems: React.FC = ({ children }) => { 6 | const { classes: styles } = useStyles(); 7 | return ( 8 |
9 |
{children}
10 |
11 | 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default NoItems; 18 | -------------------------------------------------------------------------------- /src/component/common/OperatorUpgradeAlert/OperatorUpgradeAlert.tsx: -------------------------------------------------------------------------------- 1 | import { Alert } from '@mui/material'; 2 | 3 | export const OperatorUpgradeAlert = () => { 4 | return ( 5 | 6 | Remember to update your Unleash client! New operators require new 7 | SDK versions. . 8 | 9 | ); 10 | }; 11 | 12 | const OperatorDocsLink = () => { 13 | return ( 14 | 20 | Read more 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/component/common/Proclamation/Proclamation.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()({ 4 | proclamation: { 5 | marginBottom: '1rem', 6 | }, 7 | content: { 8 | maxWidth: '800px', 9 | }, 10 | link: { 11 | display: 'block', 12 | marginTop: '0.5rem', 13 | width: '100px', 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/component/common/ProtectedRoute/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { IRoute } from 'interfaces/route'; 2 | import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; 3 | import { LoginRedirect } from 'component/common/LoginRedirect/LoginRedirect'; 4 | 5 | interface IProtectedRouteProps { 6 | route: IRoute; 7 | } 8 | 9 | export const ProtectedRoute = ({ route }: IProtectedRouteProps) => { 10 | const { user } = useAuthUser(); 11 | const isLoggedIn = Boolean(user?.id); 12 | 13 | if (!isLoggedIn && route.type === 'protected') { 14 | return ; 15 | } 16 | 17 | return ; 18 | }; 19 | -------------------------------------------------------------------------------- /src/component/common/ScrollTop/ScrollTop.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { useLocation } from 'react-router-dom'; 3 | 4 | export const ScrollTop = (): null => { 5 | const { pathname } = useLocation(); 6 | 7 | useEffect(() => { 8 | if (!noScrollPaths.some(noScroll => pathname.includes(noScroll))) { 9 | window.scrollTo(0, 0); 10 | } 11 | }, [pathname]); 12 | 13 | return null; 14 | }; 15 | 16 | const noScrollPaths = [ 17 | '/admin/api', 18 | '/admin/users', 19 | '/admin/auth', 20 | '/admin/roles', 21 | ]; 22 | -------------------------------------------------------------------------------- /src/component/common/SidebarModal/SidebarModal.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(() => ({ 4 | modal: { 5 | position: 'absolute', 6 | top: 0, 7 | right: 0, 8 | bottom: 0, 9 | height: '100vh', 10 | maxWidth: '98vw', 11 | width: 1300, 12 | overflow: 'auto', 13 | boxShadow: '0 0 1rem rgba(0, 0, 0, 0.25)', 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/common/SkipNav/SkipNavLink.tsx: -------------------------------------------------------------------------------- 1 | import { SKIP_NAV_TARGET_ID } from 'component/common/SkipNav/SkipNavTarget'; 2 | import { useStyles } from 'component/common/SkipNav/SkipNavLink.styles'; 3 | 4 | export const SkipNavLink = () => { 5 | const { classes: styles } = useStyles(); 6 | 7 | return ( 8 | 9 | Skip to content 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/component/common/SkipNav/SkipNavTarget.tsx: -------------------------------------------------------------------------------- 1 | export const SKIP_NAV_TARGET_ID = 'skip-nav-target-id'; 2 | 3 | export const SkipNavTarget = () => { 4 | return
; 5 | }; 6 | -------------------------------------------------------------------------------- /src/component/common/StatusChip/StatusChip.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | chip: { 5 | background: 'transparent', 6 | border: `1px solid ${theme.palette.primary.main}`, 7 | color: theme.palette.primary.main, 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/common/TabNav/TabNav/TabNav.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | tabNav: { 5 | backgroundColor: theme.palette.background.paper, 6 | borderBottom: '1px solid', 7 | borderBottomColor: theme.palette.grey[300], 8 | borderRadius: 0, 9 | }, 10 | tab: { 11 | [theme.breakpoints.up('lg')]: { 12 | minWidth: 160, 13 | }, 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/common/TabNav/TabPanel/TabPanel.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | 3 | interface ITabPanelProps { 4 | value: number; 5 | index: number; 6 | children: ReactNode; 7 | } 8 | 9 | export const TabPanel = ({ children, value, index }: ITabPanelProps) => ( 10 | 18 | ); 19 | -------------------------------------------------------------------------------- /src/component/common/Table/SearchHighlightContext/SearchHighlightContext.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext } from 'react'; 2 | 3 | const SearchHighlightContext = createContext(''); 4 | 5 | export const SearchHighlightProvider = SearchHighlightContext.Provider; 6 | 7 | export const useSearchHighlightContext = (): { searchQuery: string } => { 8 | return { searchQuery: useContext(SearchHighlightContext) }; 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/common/Table/SortableTableHeader/CellSortable/SortArrow/SortArrow.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | icon: { 5 | marginLeft: theme.spacing(0.25), 6 | marginRight: theme.spacing(-0.5), 7 | color: theme.palette.grey[700], 8 | fontSize: theme.fontSizes.mainHeader, 9 | verticalAlign: 'middle', 10 | }, 11 | sorted: { 12 | color: theme.palette.grey[900], 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/common/Table/Table/Table.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles<{ 4 | rowHeight: 'auto' | 'standard' | 'dense' | 'compact' | number; 5 | }>()((theme, { rowHeight }) => ({ 6 | table: { 7 | position: 'relative', 8 | 9 | '& tbody tr': { 10 | height: 11 | { 12 | auto: 'auto', 13 | standard: theme.shape.tableRowHeight, 14 | compact: theme.shape.tableRowHeightCompact, 15 | dense: theme.shape.tableRowHeightDense, 16 | }[rowHeight] ?? rowHeight, 17 | }, 18 | }, 19 | })); 20 | -------------------------------------------------------------------------------- /src/component/common/Table/Table/Table.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import classnames from 'classnames'; 3 | import { Table as MUITable, TableProps } from '@mui/material'; 4 | import { useStyles } from './Table.styles'; 5 | 6 | export const Table: FC< 7 | TableProps & { 8 | rowHeight?: 'auto' | 'dense' | 'standard' | 'compact' | number; 9 | } 10 | > = ({ rowHeight = 'auto', className, ...props }) => { 11 | const { classes } = useStyles({ rowHeight }); 12 | 13 | return ( 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/component/common/Table/TableCell/TableCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | tableCell: { 5 | padding: 0, 6 | }, 7 | })); 8 | -------------------------------------------------------------------------------- /src/component/common/Table/TableCell/TableCell.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import classnames from 'classnames'; 3 | import { TableCell as MUITableCell, TableCellProps } from '@mui/material'; 4 | import { useStyles } from './TableCell.styles'; 5 | 6 | export const TableCell: FC = ({ className, ...props }) => { 7 | const { classes: styles } = useStyles(); 8 | 9 | return ( 10 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/component/common/Table/TablePlaceholder/TablePlaceholder.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | emptyStateListItem: { 5 | border: `2px dashed ${theme.palette.neutral.light}`, 6 | padding: '0.8rem', 7 | textAlign: 'center', 8 | display: 'flex', 9 | justifyContent: 'center', 10 | alignItems: 'center', 11 | marginTop: theme.spacing(2), 12 | width: '100%', 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/common/Table/TablePlaceholder/TablePlaceholder.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Box } from '@mui/material'; 3 | import { useStyles } from 'component/common/Table/TablePlaceholder/TablePlaceholder.styles'; 4 | 5 | export const TablePlaceholder: FC = ({ children }) => { 6 | const { classes: styles } = useStyles(); 7 | 8 | return {children}; 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/common/Table/VirtualizedTable/VirtualizedTable.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(() => ({ 4 | row: { 5 | position: 'absolute', 6 | width: '100%', 7 | }, 8 | cell: { 9 | alignItems: 'center', 10 | display: 'flex', 11 | flexShrink: 0, 12 | '& > *': { 13 | flexGrow: 1, 14 | }, 15 | }, 16 | })); 17 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/ActionCell/ActionCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | justifyContent: 'center', 7 | alignItems: 'center', 8 | padding: theme.spacing(0, 1.5), 9 | }, 10 | divider: { 11 | borderColor: theme.palette.dividerAlternative, 12 | height: theme.spacing(3), 13 | margin: theme.spacing(0, 2), 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/ActionCell/ActionCell.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Divider } from '@mui/material'; 2 | import { FC, VFC } from 'react'; 3 | import { useStyles } from './ActionCell.styles'; 4 | 5 | const ActionCellDivider: VFC = () => { 6 | const { classes } = useStyles(); 7 | return ( 8 | 13 | ); 14 | }; 15 | 16 | const ActionCellComponent: FC & { 17 | Divider: typeof ActionCellDivider; 18 | } = ({ children }) => { 19 | const { classes } = useStyles(); 20 | 21 | return {children}; 22 | }; 23 | 24 | ActionCellComponent.Divider = ActionCellDivider; 25 | 26 | export const ActionCell = ActionCellComponent; 27 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/FeatureNameCell/FeatureNameCell.tsx: -------------------------------------------------------------------------------- 1 | import { VFC } from 'react'; 2 | import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; 3 | 4 | interface IFeatureNameCellProps { 5 | row: { 6 | original: { 7 | name: string; 8 | description: string; 9 | project: string; 10 | }; 11 | }; 12 | } 13 | 14 | export const FeatureNameCell: VFC = ({ row }) => ( 15 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/FeatureSeenCell/FeatureSeenCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | padding: theme.spacing(1.5), 7 | }, 8 | box: { 9 | width: '38px', 10 | height: '38px', 11 | background: 'gray', 12 | borderRadius: '4px', 13 | textAlign: 'center', 14 | display: 'flex', 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | fontSize: theme.fontSizes.smallerBody, 18 | margin: '0 auto', 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/FeatureTypeCell/FeatureTypeCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | padding: theme.spacing(1.5), 6 | display: 'flex', 7 | justifyContent: 'center', 8 | alignItems: 'center', 9 | }, 10 | icon: { 11 | color: theme.palette.inactiveIcon, 12 | }, 13 | })); 14 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/IconCell/IconCell.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@mui/material'; 2 | import React, { ReactNode } from 'react'; 3 | 4 | interface IIconCellProps { 5 | icon: ReactNode; 6 | } 7 | 8 | export const IconCell = ({ icon }: IIconCellProps) => { 9 | return ( 10 | 18 | {icon} 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/TextCell/TextCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles<{ lineClamp?: number }>()( 4 | (theme, { lineClamp }) => ({ 5 | wrapper: { 6 | padding: theme.spacing(1, 2), 7 | display: '-webkit-box', 8 | overflow: lineClamp ? 'hidden' : 'auto', 9 | WebkitLineClamp: lineClamp ? lineClamp : 'none', 10 | WebkitBoxOrient: 'vertical', 11 | wordBreak: 'break-all', 12 | }, 13 | }) 14 | ); 15 | -------------------------------------------------------------------------------- /src/component/common/Table/cells/TextCell/TextCell.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Box } from '@mui/material'; 3 | import { useStyles } from './TextCell.styles'; 4 | 5 | interface ITextCellProps { 6 | value?: string | null; 7 | lineClamp?: number; 8 | 'data-testid'?: string; 9 | } 10 | 11 | export const TextCell: FC = ({ 12 | value, 13 | children, 14 | lineClamp, 15 | 'data-testid': testid, 16 | }) => { 17 | const { classes } = useStyles({ lineClamp }); 18 | 19 | return ( 20 | 21 | 22 | {children ?? value} 23 | 24 | 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/component/common/Table/index.ts: -------------------------------------------------------------------------------- 1 | export { SortableTableHeader } from './SortableTableHeader/SortableTableHeader'; 2 | export { TableBody, TableRow } from '@mui/material'; 3 | export { Table } from './Table/Table'; 4 | export { TableCell } from './TableCell/TableCell'; 5 | export { TablePlaceholder } from './TablePlaceholder/TablePlaceholder'; 6 | export { VirtualizedTable } from './VirtualizedTable/VirtualizedTable'; 7 | -------------------------------------------------------------------------------- /src/component/common/ThemeMode/ThemeMode.tsx: -------------------------------------------------------------------------------- 1 | import UIContext from 'contexts/UIContext'; 2 | import { useContext } from 'react'; 3 | import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender'; 4 | 5 | interface IThemeModeProps { 6 | darkmode: JSX.Element; 7 | lightmode: JSX.Element; 8 | } 9 | 10 | export const ThemeMode = ({ darkmode, lightmode }: IThemeModeProps) => { 11 | const { themeMode } = useContext(UIContext); 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/component/common/ToastRenderer/ToastRenderer.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | toastWrapper: { 5 | right: 0, 6 | left: 0, 7 | margin: '0 auto', 8 | maxWidth: '450px', 9 | }, 10 | })); 11 | -------------------------------------------------------------------------------- /src/component/common/TooltipResolver/TooltipResolver.tsx: -------------------------------------------------------------------------------- 1 | import { Tooltip, TooltipProps } from '@mui/material'; 2 | 3 | export interface ITooltipResolverProps extends Omit { 4 | title: string | undefined; 5 | } 6 | 7 | export const TooltipResolver = ({ 8 | title, 9 | children, 10 | ...rest 11 | }: ITooltipResolverProps) => { 12 | if (!title) { 13 | return children; 14 | } 15 | 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/component/common/UpdateButton/UpdateButton.tsx: -------------------------------------------------------------------------------- 1 | import PermissionButton, { 2 | IPermissionButtonProps, 3 | } from '../PermissionButton/PermissionButton'; 4 | 5 | export const UpdateButton = ({ ...rest }: IPermissionButtonProps) => { 6 | return ( 7 | 8 | Save 9 | 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /src/component/common/flags.ts: -------------------------------------------------------------------------------- 1 | export const P = 'P'; 2 | export const C = 'C'; 3 | export const E = 'E'; 4 | export const EEA = 'EEA'; 5 | export const RE = 'RE'; 6 | export const SE = 'SE'; 7 | export const UG = 'UG'; 8 | -------------------------------------------------------------------------------- /src/component/context/ContectFormChip/ContextFormChipList.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | listStyleType: 'none', 6 | display: 'flex', 7 | flexWrap: 'wrap', 8 | gap: '0.5rem', 9 | padding: 0, 10 | margin: 0, 11 | marginBottom: '1rem !important', 12 | }, 13 | })); 14 | -------------------------------------------------------------------------------- /src/component/context/ContectFormChip/ContextFormChipList.tsx: -------------------------------------------------------------------------------- 1 | import { useStyles } from 'component/context/ContectFormChip/ContextFormChipList.styles'; 2 | import React from 'react'; 3 | 4 | export const ContextFormChipList: React.FC = ({ children }) => { 5 | const { classes: styles } = useStyles(); 6 | 7 | return
    {children}
; 8 | }; 9 | -------------------------------------------------------------------------------- /src/component/context/CreateUnleashContext/CreateUnleashContextPage.tsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { CreateUnleashContext } from 'component/context/CreateUnleashContext/CreateUnleashContext'; 3 | import { GO_BACK } from 'constants/navigate'; 4 | 5 | export const CreateUnleashContextPage = () => { 6 | const navigate = useNavigate(); 7 | 8 | return ( 9 | navigate('/context')} 11 | onCancel={() => navigate(GO_BACK)} 12 | /> 13 | ); 14 | }; 15 | -------------------------------------------------------------------------------- /src/component/environments/EnvironmentDeleteConfirm/EnvironmentDeleteConfirm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | deleteParagraph: { 5 | marginTop: '2rem', 6 | }, 7 | environmentDeleteInput: { 8 | marginTop: '1rem', 9 | }, 10 | })); 11 | -------------------------------------------------------------------------------- /src/component/environments/EnvironmentForm/EnvironmentTypeSelector/EnvironmentTypeSelector.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | radioGroup: { 5 | flexDirection: 'row', 6 | }, 7 | formHeader: { 8 | fontWeight: 'bold', 9 | fontSize: theme.fontSizes.bodySize, 10 | marginTop: '1.5rem', 11 | marginBottom: '0.5rem', 12 | }, 13 | radioBtnGroup: { display: 'flex', flexDirection: 'column' }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/events/EventPage/EventPage.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { ADMIN } from 'component/providers/AccessProvider/permissions'; 3 | import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; 4 | import AccessContext from 'contexts/AccessContext'; 5 | import { AdminAlert } from 'component/common/AdminAlert/AdminAlert'; 6 | import { EventLog } from 'component/events/EventLog/EventLog'; 7 | 8 | export const EventPage = () => { 9 | const { hasAccess } = useContext(AccessContext); 10 | 11 | return ( 12 | } 15 | elseShow={} 16 | /> 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/component/feature/CopyFeature/CopyFeature.module.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | padding: var(--card-header-padding); 3 | border: var(--default-border); 4 | } 5 | 6 | .header h1 { 7 | font-size: var(--h1-size); 8 | } 9 | 10 | .content { 11 | padding: var(--card-padding); 12 | } 13 | 14 | .content form { 15 | display: flex; 16 | flex-direction: column; 17 | max-width: 400px; 18 | } 19 | 20 | .content > *, 21 | .content form > * { 22 | margin: 1rem 0; 23 | } 24 | 25 | .text { 26 | max-width: 400px; 27 | } 28 | -------------------------------------------------------------------------------- /src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegment.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | divider: { 5 | border: `1px dashed ${theme.palette.divider}`, 6 | marginTop: theme.spacing(1), 7 | marginBottom: theme.spacing(2), 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureToggleList/FeatureStaleCell/FeatureStaleCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | status: { 5 | color: theme.palette.success.dark, 6 | fontSize: 'inherit', 7 | }, 8 | stale: { 9 | color: theme.palette.error.dark, 10 | }, 11 | })); 12 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureLog/FeatureLog.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | borderRadius: '12.5px', 6 | backgroundColor: theme.palette.background.paper, 7 | padding: '2rem', 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureMetrics/FeatureMetrics.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | mobileMarginTop: { 5 | [theme.breakpoints.down('md')]: { 6 | marginTop: theme.spacing(2), 7 | }, 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureNotFound/FeatureNotFound.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | featureId: { 5 | wordBreak: 'break-all', 6 | }, 7 | })); 8 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/AddTagDialog/AddTagDialog.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | dialogFormContent: { 5 | ['& > *']: { 6 | margin: '0.5rem 0', 7 | }, 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/FeatureOverview.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | width: '100%', 7 | [theme.breakpoints.down(1000)]: { 8 | flexDirection: 'column', 9 | }, 10 | }, 11 | mainContent: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | width: `calc(100% - (350px + 1rem))`, 15 | [theme.breakpoints.down(1000)]: { 16 | width: '100%', 17 | }, 18 | }, 19 | trafficContainer: { 20 | display: 'flex', 21 | flexWrap: 'wrap', 22 | [theme.breakpoints.down(1000)]: { 23 | marginTop: '1rem', 24 | }, 25 | }, 26 | })); 27 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvSwitches/FeatureOverviewEnvSwitch/FeatureOverviewEnvSwitch.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | label: { 5 | display: 'inline-flex', 6 | alignItems: 'center', 7 | cursor: 'pointer', 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | accordionBodyInnerContainer: { 5 | [theme.breakpoints.down(400)]: { 6 | padding: '0.5rem', 7 | }, 8 | }, 9 | accordionBody: { 10 | width: '100%', 11 | position: 'relative', 12 | paddingBottom: '1rem', 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/ConstraintItem/ConstraintItem.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | width: '100%', 6 | padding: theme.spacing(2, 3), 7 | borderRadius: theme.shape.borderRadiusMedium, 8 | border: `1px solid ${theme.palette.divider}`, 9 | }, 10 | chip: { 11 | margin: '0.25rem', 12 | }, 13 | paragraph: { 14 | display: 'inline', 15 | margin: '0.25rem 0', 16 | maxWidth: '95%', 17 | textAlign: 'center', 18 | wordBreak: 'break-word', 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyExecution/StrategyExecution.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | valueContainer: { 5 | padding: theme.spacing(2, 3), 6 | border: `1px solid ${theme.palette.dividerAlternative}`, 7 | borderRadius: theme.shape.borderRadiusMedium, 8 | }, 9 | valueSeparator: { 10 | color: theme.palette.grey[700], 11 | }, 12 | })); 13 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureSettings/FeatureSettingsInformation/FeatureSettingsInformation.style.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | alignItems: 'center', 7 | }, 8 | header: { 9 | fontSize: theme.fontSizes.mainHeader, 10 | }, 11 | })); 12 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureSettings/FeatureSettingsProject/FeatureSettingsProjectConfirm/FeatureSettingsProjectConfirm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'grid', 6 | gap: theme.spacing(2), 7 | paddingTop: theme.spacing(2), 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureStatus/FeatureStatus.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | width: '42px', 6 | height: '42px', 7 | fontSize: '0.7em', 8 | background: 'gray', 9 | borderRadius: theme.shape.borderRadius, 10 | textAlign: 'center', 11 | display: 'flex', 12 | alignItems: 'center', 13 | justifyContent: 'center', 14 | padding: '13px 10px', 15 | }, 16 | })); 17 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureType/FeatureType.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | icon: { 5 | color: theme.palette.inactiveIcon, 6 | }, 7 | })); 8 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureVariants/FeatureVariants.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | borderRadius: '12.5px', 6 | backgroundColor: '#fff', 7 | padding: '2rem', 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureVariants/FeatureVariants.tsx: -------------------------------------------------------------------------------- 1 | import { FeatureVariantsList } from './FeatureVariantsList/FeatureVariantsList'; 2 | import { usePageTitle } from 'hooks/usePageTitle'; 3 | 4 | const FeatureVariants = () => { 5 | usePageTitle('Variants'); 6 | 7 | return ; 8 | }; 9 | 10 | export default FeatureVariants; 11 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/OverrideConfig/OverrideConfig.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | contextFieldSelect: { 5 | marginRight: '8px', 6 | }, 7 | })); 8 | -------------------------------------------------------------------------------- /src/component/feature/FeatureView/FeatureVariants/FeatureVariantsList/AddFeatureVariant/enums.ts: -------------------------------------------------------------------------------- 1 | export const weightTypes = { 2 | FIX: 'fix', 3 | VARIABLE: 'variable', 4 | }; 5 | -------------------------------------------------------------------------------- /src/component/feature/StrategyTypes/DefaultStrategy/DefaultStrategy.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { IStrategy } from 'interfaces/strategy'; 3 | 4 | interface IDefaultStrategyProps { 5 | strategyDefinition: IStrategy; 6 | } 7 | 8 | const DefaultStrategy = ({ strategyDefinition }: IDefaultStrategyProps) => { 9 | return

{strategyDefinition?.description}

; 10 | }; 11 | 12 | export default DefaultStrategy; 13 | -------------------------------------------------------------------------------- /src/component/feedback/FeedbackCES/FeedbackCESForm.test.tsx: -------------------------------------------------------------------------------- 1 | import { FeedbackCESForm } from './FeedbackCESForm'; 2 | import { ThemeProvider } from 'themes/ThemeProvider'; 3 | import { render } from 'utils/testRenderer'; 4 | 5 | test('FeedbackCESForm', () => { 6 | const onClose = () => { 7 | throw new Error('Unexpected onClose call.'); 8 | }; 9 | 10 | render( 11 | 12 | 16 | 17 | ); 18 | 19 | expect(document.body).toMatchSnapshot(); 20 | }); 21 | -------------------------------------------------------------------------------- /src/component/feedback/FeedbackCESContext/useFeedbackCESEnabled.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isLocalhostDomain, 3 | isUnleashDomain, 4 | isVercelBranchDomain, 5 | } from 'utils/env'; 6 | 7 | export const useFeedbackCESEnabled = (): boolean => { 8 | return isUnleashDomain() || isVercelBranchDomain() || isLocalhostDomain(); 9 | }; 10 | -------------------------------------------------------------------------------- /src/component/menu/Footer/Footer.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | footer: { 5 | padding: '2rem 4rem', 6 | width: '100%', 7 | flexGrow: 1, 8 | zIndex: 100, 9 | backgroundColor: theme.palette.footerBackground, 10 | }, 11 | list: { 12 | padding: 0, 13 | margin: 0, 14 | }, 15 | listItem: { 16 | padding: 0, 17 | margin: 0, 18 | '& a': { 19 | textDecoration: 'none', 20 | color: theme.palette.text.primary, 21 | }, 22 | }, 23 | })); 24 | -------------------------------------------------------------------------------- /src/component/menu/Footer/FooterTitle.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | title: { 5 | all: 'unset', 6 | display: 'block', 7 | margin: '1rem 0', 8 | fontSize: '1rem', 9 | fontWeight: theme.fontWeight.bold, 10 | }, 11 | })); 12 | -------------------------------------------------------------------------------- /src/component/menu/Footer/FooterTitle.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { useStyles } from 'component/menu/Footer/FooterTitle.styles'; 3 | 4 | interface IFooterTitleProps { 5 | children: ReactNode; 6 | } 7 | 8 | export const FooterTitle = ({ children }: IFooterTitleProps) => { 9 | const { classes: styles } = useStyles(); 10 | 11 | return

{children}

; 12 | }; 13 | -------------------------------------------------------------------------------- /src/component/menu/__tests__/routes.test.tsx: -------------------------------------------------------------------------------- 1 | import { baseRoutes, getRoute } from '../routes'; 2 | 3 | test('returns all baseRoutes', () => { 4 | expect(baseRoutes).toMatchSnapshot(); 5 | }); 6 | 7 | test('getRoute() returns named route', () => { 8 | const featuresRoute = getRoute('/features'); 9 | expect(featuresRoute?.path).toEqual('/features'); 10 | }); 11 | -------------------------------------------------------------------------------- /src/component/playground/Playground/LazyPlayground.tsx: -------------------------------------------------------------------------------- 1 | import { lazy } from 'react'; 2 | 3 | export const LazyPlayground = lazy(() => import('./Playground')); 4 | -------------------------------------------------------------------------------- /src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureResultInfoPopoverCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | popoverPaper: { 5 | display: 'flex', 6 | flexDirection: 'column', 7 | padding: theme.spacing(6), 8 | width: 728, 9 | maxWidth: '100%', 10 | height: 'auto', 11 | overflowY: 'auto', 12 | backgroundColor: theme.palette.tertiary.light, 13 | borderRadius: theme.shape.borderRadiusLarge, 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/StrategyExecution/SegmentExecution/SegmentExecution.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: {}, 5 | link: { 6 | textDecoration: 'none', 7 | marginLeft: theme.spacing(1), 8 | '&:hover': { 9 | textDecoration: 'underline', 10 | }, 11 | }, 12 | })); 13 | -------------------------------------------------------------------------------- /src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/StrategyExecution/StrategyExecution.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | valueContainer: { 5 | display: 'flex', 6 | alignItems: 'center', 7 | gap: '1ch', 8 | }, 9 | valueSeparator: { 10 | color: theme.palette.grey[700], 11 | }, 12 | summary: { 13 | width: 'auto', 14 | height: 'auto', 15 | padding: theme.spacing(2, 3), 16 | borderRadius: theme.shape.borderRadiusMedium, 17 | border: `1px solid ${theme.palette.dividerAlternative}`, 18 | }, 19 | })); 20 | -------------------------------------------------------------------------------- /src/component/playground/Playground/PlaygroundResultsTable/FeatureResultInfoPopoverCell/FeatureStrategyList/StrategyList/StrategyItem/StrategyExecution/helpers.ts: -------------------------------------------------------------------------------- 1 | export const getMappedParam = (key: string) => { 2 | switch (key.toUpperCase()) { 3 | case 'USERIDS': 4 | return 'userId'; 5 | case 'IPS': 6 | return 'remoteAddress'; 7 | default: 8 | return key; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectFeatureToggles/ActionsCell/ActionsCell.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | cell: { 5 | display: 'flex', 6 | justifyContent: 'center', 7 | paddingRight: theme.spacing(2), 8 | }, 9 | menuContainer: { 10 | borderRadius: theme.shape.borderRadiusLarge, 11 | padding: theme.spacing(1, 1.5), 12 | }, 13 | item: { 14 | borderRadius: theme.shape.borderRadius, 15 | }, 16 | text: { 17 | fontSize: theme.fontSizes.smallBody, 18 | }, 19 | })); 20 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/FeatureToggleSwitch.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | mx: 'auto', 6 | display: 'flex', 7 | justifyContent: 'center', 8 | }, 9 | })); 10 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectFeatureToggles/FeatureToggleSwitch/hooks/useOptimisticUpdate.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react'; 2 | 3 | export const useOptimisticUpdate = (state: T) => { 4 | const [value, setValue] = useState(state); 5 | 6 | const rollback = useCallback(() => setValue(state), [state]); 7 | 8 | useEffect(() => { 9 | setValue(state); 10 | }, [state]); 11 | 12 | return [value, setValue, rollback] as const; 13 | }; 14 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectFeatureToggles/hooks/useEnvironmentsRef.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from 'react'; 2 | 3 | /** 4 | * Don't revalidate if array content didn't change. 5 | * Needed for `columns` memo optimization. 6 | */ 7 | export const useEnvironmentsRef = (environments: string[] = []) => { 8 | const ref = useRef(environments); 9 | 10 | if (environments?.join('') !== ref.current?.join('')) { 11 | ref.current = environments; 12 | } 13 | 14 | return ref.current; 15 | }; 16 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectFeaturesArchive/ProjectFeaturesArchive.tsx: -------------------------------------------------------------------------------- 1 | import { ProjectFeaturesArchiveTable } from 'component/archive/ProjectFeaturesArchiveTable'; 2 | import { usePageTitle } from 'hooks/usePageTitle'; 3 | import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; 4 | import { useProjectNameOrId } from 'hooks/api/getters/useProject/useProject'; 5 | 6 | export const ProjectFeaturesArchive = () => { 7 | const projectId = useRequiredPathParam('projectId'); 8 | const projectName = useProjectNameOrId(projectId); 9 | usePageTitle(`Project archive – ${projectName}`); 10 | 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /src/component/project/Project/ProjectHealth/ReportTable/utils.ts: -------------------------------------------------------------------------------- 1 | import differenceInDays from 'date-fns/differenceInDays'; 2 | import { EXPERIMENT, OPERATIONAL, RELEASE } from 'constants/featureToggleTypes'; 3 | 4 | const FORTY_DAYS = 40; 5 | const SEVEN_DAYS = 7; 6 | 7 | export const toggleExpiryByTypeMap: Record = { 8 | [EXPERIMENT]: FORTY_DAYS, 9 | [RELEASE]: FORTY_DAYS, 10 | [OPERATIONAL]: SEVEN_DAYS, 11 | }; 12 | 13 | export const getDiffInDays = (date: Date, now: Date) => { 14 | return Math.abs(differenceInDays(date, now)); 15 | }; 16 | 17 | export const expired = (diff: number, type: string) => { 18 | return diff >= toggleExpiryByTypeMap[type]; 19 | }; 20 | -------------------------------------------------------------------------------- /src/component/project/ProjectAccess/ProjectAccess.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | pageContent: { 5 | minHeight: '200px', 6 | }, 7 | divider: { 8 | height: '1px', 9 | position: 'relative', 10 | left: 0, 11 | right: 0, 12 | backgroundColor: theme.palette.divider, 13 | margin: theme.spacing(4, -4, 3), 14 | }, 15 | inputLabel: { backgroundColor: '#fff' }, 16 | roleName: { 17 | fontWeight: 'bold', 18 | padding: '5px 0px', 19 | }, 20 | menuItem: { 21 | width: '340px', 22 | whiteSpace: 'normal', 23 | }, 24 | projectRoleSelect: { 25 | minWidth: '150px', 26 | }, 27 | })); 28 | -------------------------------------------------------------------------------- /src/component/project/ProjectEnvironment/EnvironmentDisableConfirm/EnvironmentDisableConfirm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | deleteParagraph: { 5 | marginTop: '2rem', 6 | }, 7 | environmentDeleteInput: { 8 | marginTop: '1rem', 9 | }, 10 | })); 11 | -------------------------------------------------------------------------------- /src/component/project/ProjectEnvironment/ProjectEnvironment.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | flexWrap: 'wrap', 7 | [theme.breakpoints.down('sm')]: { 8 | justifyContent: 'center', 9 | }, 10 | }, 11 | apiError: { 12 | maxWidth: '400px', 13 | marginBottom: '1rem', 14 | }, 15 | cardLink: { 16 | color: 'inherit', 17 | textDecoration: 'none', 18 | border: 'none', 19 | padding: '0', 20 | background: 'transparent', 21 | fontFamily: theme.typography.fontFamily, 22 | pointer: 'cursor', 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /src/component/project/ProjectEnvironment/helpers.ts: -------------------------------------------------------------------------------- 1 | import { IProjectEnvironment } from 'interfaces/environments'; 2 | 3 | export const getEnabledEnvs = (envs: IProjectEnvironment[]) => { 4 | return envs.reduce((enabledEnvs, currentEnv) => { 5 | if (currentEnv.enabled) { 6 | return enabledEnvs + 1; 7 | } 8 | return enabledEnvs; 9 | }, 0); 10 | }; 11 | -------------------------------------------------------------------------------- /src/component/project/ProjectList/ProjectList.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | flexWrap: 'wrap', 7 | [theme.breakpoints.down('sm')]: { 8 | justifyContent: 'center', 9 | }, 10 | }, 11 | apiError: { 12 | maxWidth: '400px', 13 | marginBottom: '1rem', 14 | }, 15 | cardLink: { 16 | color: 'inherit', 17 | textDecoration: 'none', 18 | border: 'none', 19 | padding: '0', 20 | background: 'transparent', 21 | fontFamily: theme.typography.fontFamily, 22 | pointer: 'cursor', 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /src/component/providers/UIProvider/UIProviderContainer.tsx: -------------------------------------------------------------------------------- 1 | import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; 2 | import UIProvider from './UIProvider'; 3 | 4 | export const UIProviderContainer: React.FC = ({ children }) => { 5 | const { uiConfig } = useUiConfig(); 6 | 7 | if (!uiConfig.flags) { 8 | return null; 9 | } 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /src/component/segments/CreateSegmentButton/CreateSegmentButton.tsx: -------------------------------------------------------------------------------- 1 | import { CREATE_SEGMENT } from 'component/providers/AccessProvider/permissions'; 2 | import PermissionButton from 'component/common/PermissionButton/PermissionButton'; 3 | import { NAVIGATE_TO_CREATE_SEGMENT } from 'utils/testIds'; 4 | import { useNavigate } from 'react-router-dom'; 5 | 6 | export const CreateSegmentButton = () => { 7 | const navigate = useNavigate(); 8 | 9 | return ( 10 | navigate('/segments/create')} 12 | permission={CREATE_SEGMENT} 13 | data-testid={NAVIGATE_TO_CREATE_SEGMENT} 14 | > 15 | New segment 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/component/segments/SegmentActionCell/SegmentActionCell.tsx: -------------------------------------------------------------------------------- 1 | import { ActionCell } from 'component/common/Table/cells/ActionCell/ActionCell'; 2 | import { ISegment } from 'interfaces/segment'; 3 | import { RemoveSegmentButton } from 'component/segments/RemoveSegmentButton/RemoveSegmentButton'; 4 | import { EditSegmentButton } from 'component/segments/EditSegmentButton/EditSegmentButton'; 5 | 6 | interface ISegmentActionCellProps { 7 | segment: ISegment; 8 | } 9 | 10 | export const SegmentActionCell = ({ segment }: ISegmentActionCellProps) => { 11 | return ( 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/component/segments/SegmentDelete/SegmentDeleteConfirm/SegmentDeleteConfirm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | deleteParagraph: { 5 | marginTop: '2rem', 6 | }, 7 | deleteInput: { 8 | marginTop: '1rem', 9 | }, 10 | link: { 11 | textDecoration: 'none', 12 | color: theme.palette.primary.main, 13 | fontWeight: theme.fontWeight.bold, 14 | }, 15 | })); 16 | -------------------------------------------------------------------------------- /src/component/segments/hooks/useSegmentValuesCount.ts: -------------------------------------------------------------------------------- 1 | import { IConstraint } from 'interfaces/strategy'; 2 | import { useMemo } from 'react'; 3 | 4 | export const useSegmentValuesCount = (constraints: IConstraint[]): number => { 5 | return useMemo(() => { 6 | return constraints 7 | .map(constraint => constraint.values) 8 | .reduce((acc, values) => acc + (values?.length ?? 0), 0); 9 | }, [constraints]); 10 | }; 11 | -------------------------------------------------------------------------------- /src/component/splash/splash.tsx: -------------------------------------------------------------------------------- 1 | // All known splash IDs. 2 | export const splashIds = ['operators'] as const; 3 | 4 | // Active splash IDs that may be shown to the user. 5 | export const activeSplashIds: SplashId[] = []; 6 | 7 | export type SplashId = typeof splashIds[number]; 8 | -------------------------------------------------------------------------------- /src/component/user/DemoAuth/DemoAuth.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 350px; 3 | } 4 | 5 | .container > * { 6 | margin: 0.6rem 0; 7 | } 8 | 9 | .emailField { 10 | width: 100%; 11 | } 12 | 13 | .form > * { 14 | margin: 0.6rem 0; 15 | } 16 | 17 | .logo { 18 | margin: 0 auto; 19 | display: block; 20 | height: 100px; 21 | width: 100px; 22 | } 23 | 24 | .button { 25 | min-width: 150px; 26 | margin: 1rem auto; 27 | display: block; 28 | } 29 | 30 | @media (max-width: 500px) { 31 | .container { 32 | width: 100%; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/component/user/ForgottenPassword/ForgottenPassword.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | forgottenPassword: { 5 | width: '350px', 6 | [theme.breakpoints.down('sm')]: { 7 | width: '100%', 8 | }, 9 | }, 10 | button: { 11 | width: '150px', 12 | margin: '1rem auto', 13 | }, 14 | email: { 15 | display: 'block', 16 | margin: '0.5rem 0', 17 | }, 18 | loginText: { 19 | textAlign: 'center', 20 | }, 21 | })); 22 | -------------------------------------------------------------------------------- /src/component/user/ForgottenPassword/ForgottenPassword.test.tsx: -------------------------------------------------------------------------------- 1 | import { screen } from '@testing-library/react'; 2 | import { FORGOTTEN_PASSWORD_FIELD } from 'utils/testIds'; 3 | import React from 'react'; 4 | import ForgottenPassword from 'component/user/ForgottenPassword/ForgottenPassword'; 5 | import { render } from 'utils/testRenderer'; 6 | 7 | test('should render password auth', async () => { 8 | render(); 9 | 10 | await screen.findByTestId(FORGOTTEN_PASSWORD_FIELD); 11 | }); 12 | -------------------------------------------------------------------------------- /src/component/user/HostedAuth/HostedAuth.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | loginContainer: { 5 | minWidth: '350px', 6 | [theme.breakpoints.down('sm')]: { 7 | width: '100%', 8 | minWidth: 'auto', 9 | }, 10 | }, 11 | contentContainer: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | }, 15 | apiError: { 16 | color: theme.palette.error.main, 17 | }, 18 | button: { 19 | width: '150px', 20 | margin: '1rem auto 0 auto', 21 | display: 'block', 22 | textAlign: 'center', 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /src/component/user/Login/parseRedirectParam.ts: -------------------------------------------------------------------------------- 1 | interface IRedirectParam { 2 | pathname: string; 3 | search: string; 4 | } 5 | 6 | export const parseRedirectParam = (redirect: string): IRedirectParam => { 7 | const url = new URL( 8 | decodeURIComponent(redirect), 9 | window.location.protocol + '//' + window.location.host 10 | ); 11 | 12 | return { 13 | pathname: url.pathname, 14 | search: url.search, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/component/user/PasswordAuth/PasswordAuth.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | loginContainer: { 5 | minWidth: '350px', 6 | [theme.breakpoints.down('sm')]: { 7 | width: '100%', 8 | minWidth: 'auto', 9 | }, 10 | }, 11 | contentContainer: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | }, 15 | apiError: { 16 | color: theme.palette.error.main, 17 | marginBottom: '0.5rem', 18 | }, 19 | })); 20 | -------------------------------------------------------------------------------- /src/component/user/ResetPassword/ResetPassword.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | resetPassword: { 5 | width: '350px', 6 | maxWidth: '350px', 7 | [theme.breakpoints.down('sm')]: { 8 | width: '100%', 9 | }, 10 | }, 11 | container: { 12 | display: 'flex', 13 | }, 14 | innerContainer: { width: '40%', minHeight: '100vh' }, 15 | title: { 16 | fontWeight: 'bold', 17 | fontSize: '1.2rem', 18 | marginBottom: '1rem', 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /src/component/user/SimpleAuth/SimpleAuth.module.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 350px; 3 | } 4 | 5 | .container > * { 6 | margin: 0.6rem 0; 7 | width: 100%; 8 | } 9 | 10 | .button { 11 | min-width: 150px; 12 | margin: 0 auto; 13 | display: block; 14 | } 15 | 16 | @media (max-width: 600px) { 17 | .container { 18 | width: 100%; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/component/user/UserProfile/EditProfile/EditProfile.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | width: '100%', 6 | transform: 'translateY(-30px)', 7 | }, 8 | form: { 9 | width: '100%', 10 | '& > *': { 11 | width: '100%', 12 | }, 13 | }, 14 | editProfileTitle: { 15 | fontWeight: 'bold', 16 | }, 17 | button: { 18 | width: '150px', 19 | marginTop: '1.15rem', 20 | [theme.breakpoints.down('md')]: { 21 | width: '100px', 22 | }, 23 | }, 24 | })); 25 | -------------------------------------------------------------------------------- /src/component/user/UserProfile/UserProfile.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | userProfileMenu: { 5 | display: 'flex', 6 | }, 7 | profileContainer: { 8 | position: 'relative', 9 | }, 10 | button: { 11 | color: 'inherit', 12 | padding: '0.2rem 1rem', 13 | '&:hover': { 14 | backgroundColor: 'transparent', 15 | }, 16 | }, 17 | icon: { 18 | color: theme.palette.grey[700], 19 | }, 20 | })); 21 | -------------------------------------------------------------------------------- /src/component/user/UserProfile/index.tsx: -------------------------------------------------------------------------------- 1 | import UserProfile from './UserProfile'; 2 | import { useLocationSettings } from 'hooks/useLocationSettings'; 3 | import { useAuthUser } from 'hooks/api/getters/useAuth/useAuthUser'; 4 | 5 | const UserProfileContainer = () => { 6 | const { locationSettings, setLocationSettings } = useLocationSettings(); 7 | const { user } = useAuthUser(); 8 | 9 | if (!user) { 10 | return null; 11 | } 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | 22 | export default UserProfileContainer; 23 | -------------------------------------------------------------------------------- /src/component/user/common/ResetPasswordDetails/ResetPasswordDetails.tsx: -------------------------------------------------------------------------------- 1 | import { FC, Dispatch, SetStateAction } from 'react'; 2 | import ResetPasswordForm from '../ResetPasswordForm/ResetPasswordForm'; 3 | 4 | interface IResetPasswordDetails { 5 | token: string; 6 | setLoading: Dispatch>; 7 | } 8 | 9 | const ResetPasswordDetails: FC = ({ 10 | children, 11 | token, 12 | setLoading, 13 | }) => { 14 | return ( 15 |
16 | {children} 17 | 18 |
19 | ); 20 | }; 21 | 22 | export default ResetPasswordDetails; 23 | -------------------------------------------------------------------------------- /src/component/user/common/ResetPasswordError/ResetPasswordError.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertTitle } from '@mui/material'; 2 | 3 | const ResetPasswordError = () => { 4 | return ( 5 | 6 | Unable to reset password 7 | Something went wrong when attempting to update your password. This 8 | could be due to unstable internet connectivity. If retrying the 9 | request does not work, please try again later. 10 | 11 | ); 12 | }; 13 | 14 | export default ResetPasswordError; 15 | -------------------------------------------------------------------------------- /src/component/user/common/ResetPasswordForm/PasswordMatcher/PasswordMatcher.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | matcherContainer: { 5 | position: 'relative', 6 | }, 7 | matcherIcon: { 8 | marginRight: '5px', 9 | }, 10 | matcher: { 11 | position: 'absolute', 12 | bottom: '-8px', 13 | display: 'flex', 14 | alignItems: 'center', 15 | }, 16 | matcherError: { 17 | color: theme.palette.error.main, 18 | }, 19 | matcherSuccess: { 20 | color: theme.palette.primary.main, 21 | }, 22 | })); 23 | -------------------------------------------------------------------------------- /src/component/user/common/ResetPasswordForm/ResetPasswordForm.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | display: 'flex', 6 | flexDirection: 'column', 7 | position: 'relative', 8 | }, 9 | button: { 10 | width: '150px', 11 | margin: '1rem auto', 12 | display: 'block', 13 | }, 14 | })); 15 | -------------------------------------------------------------------------------- /src/component/user/common/ResetPasswordSuccess/ResetPasswordSuccess.tsx: -------------------------------------------------------------------------------- 1 | import { Alert, AlertTitle } from '@mui/material'; 2 | 3 | const ResetPasswordSuccess = () => { 4 | return ( 5 | 6 | Success 7 | You successfully reset your password. 8 | 9 | ); 10 | }; 11 | 12 | export default ResetPasswordSuccess; 13 | -------------------------------------------------------------------------------- /src/component/user/common/SecondaryLoginActions/SecondaryLoginActions.styles.ts: -------------------------------------------------------------------------------- 1 | import { makeStyles } from 'tss-react/mui'; 2 | 3 | export const useStyles = makeStyles()(theme => ({ 4 | container: { 5 | margin: 'auto auto 0 auto', 6 | width: '230px', 7 | [theme.breakpoints.down('md')]: { 8 | marginTop: '1rem', 9 | }, 10 | }, 11 | link: { 12 | fontWeight: 'bold', 13 | textAlign: 'center', 14 | }, 15 | text: { fontWeight: 'bold', marginBottom: '0.5rem' }, 16 | })); 17 | -------------------------------------------------------------------------------- /src/component/user/user.module.scss: -------------------------------------------------------------------------------- 1 | .showUser img { 2 | border-radius: 25px; 3 | height: 32px; 4 | border: 2px solid #ffffff; 5 | } 6 | 7 | .showLocale { 8 | display: flex; 9 | align-items: center; 10 | } 11 | 12 | .labelFlag, 13 | .showLocale img { 14 | border-radius: 2px; 15 | width: 30px; 16 | height: 18px; 17 | z-index: 6000; 18 | margin: 0 10px; 19 | } 20 | 21 | .avatar { 22 | width: 30px; 23 | height: 30px; 24 | } 25 | 26 | .showUserSettings { 27 | display: flex; 28 | align-items: center; 29 | width: 100%; 30 | } 31 | 32 | .dropdown { 33 | color: #000; 34 | 35 | font-weight: normal; 36 | } 37 | -------------------------------------------------------------------------------- /src/constants/apiErrors.ts: -------------------------------------------------------------------------------- 1 | export const ENVIRONMENT_STRATEGY_ERROR = 2 | 'You can not enable the environment before it has strategies'; 3 | -------------------------------------------------------------------------------- /src/constants/authTypes.ts: -------------------------------------------------------------------------------- 1 | /* AUTH TYPES */ 2 | export const SIMPLE_TYPE = 'unsecure'; 3 | export const DEMO_TYPE = 'demo'; 4 | export const PASSWORD_TYPE = 'password'; 5 | export const HOSTED_TYPE = 'enterprise-hosted'; 6 | -------------------------------------------------------------------------------- /src/constants/environmentTypes.ts: -------------------------------------------------------------------------------- 1 | export const PRODUCTION = 'production'; 2 | -------------------------------------------------------------------------------- /src/constants/featureToggleTypes.ts: -------------------------------------------------------------------------------- 1 | export const EXPERIMENT = 'experiment'; 2 | export const RELEASE = 'release'; 3 | export const OPERATIONAL = 'operational'; 4 | export const KILLSWITCH = 'kill-switch'; 5 | export const PERMISSION = 'permission'; 6 | -------------------------------------------------------------------------------- /src/constants/misc.ts: -------------------------------------------------------------------------------- 1 | export const EDIT = 'Edit'; 2 | export const CREATE = 'Create'; 3 | -------------------------------------------------------------------------------- /src/constants/navigate.ts: -------------------------------------------------------------------------------- 1 | export const GO_BACK = -1; 2 | -------------------------------------------------------------------------------- /src/constants/statusCodes.ts: -------------------------------------------------------------------------------- 1 | export const BAD_REQUEST = 400; 2 | export const OK = 200; 3 | export const NOT_FOUND = 404; 4 | export const FORBIDDEN = 403; 5 | export const UNAUTHORIZED = 401; 6 | -------------------------------------------------------------------------------- /src/contexts/AccessContext.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export interface IAccessContext { 4 | isAdmin: boolean; 5 | hasAccess: ( 6 | permission: string, 7 | project?: string, 8 | environment?: string 9 | ) => boolean; 10 | } 11 | 12 | const hasAccessPlaceholder = () => { 13 | throw new Error('hasAccess called outside AccessContext'); 14 | }; 15 | 16 | const AccessContext = React.createContext({ 17 | isAdmin: false, 18 | hasAccess: hasAccessPlaceholder, 19 | }); 20 | 21 | export default AccessContext; 22 | -------------------------------------------------------------------------------- /src/hooks/api/actions/useInstanceStatusApi/useInstanceStatusApi.ts: -------------------------------------------------------------------------------- 1 | import useAPI from '../useApi/useApi'; 2 | 3 | const useInstanceStatusApi = () => { 4 | const { makeRequest, createRequest, errors, loading } = useAPI({ 5 | propagateErrors: true, 6 | }); 7 | 8 | const extendTrial = async (): Promise => { 9 | const path = 'api/instance/extend'; 10 | const req = createRequest(path, { method: 'POST' }, 'extendTrial'); 11 | await makeRequest(req.caller, req.id); 12 | }; 13 | 14 | return { 15 | extendTrial, 16 | loading, 17 | errors, 18 | }; 19 | }; 20 | 21 | export default useInstanceStatusApi; 22 | -------------------------------------------------------------------------------- /src/hooks/api/actions/useSplashApi/useSplashApi.ts: -------------------------------------------------------------------------------- 1 | import useAPI from '../useApi/useApi'; 2 | 3 | const useSplashApi = () => { 4 | const { makeRequest, createRequest } = useAPI({ 5 | propagateErrors: true, 6 | }); 7 | 8 | const setSplashSeen = async (splashId: string) => { 9 | const path = `api/admin/splash/${splashId}`; 10 | const req = createRequest(path, { 11 | method: 'POST', 12 | }); 13 | 14 | try { 15 | const res = await makeRequest(req.caller, req.id); 16 | return res; 17 | } catch (e) { 18 | console.log('An exception was caught and handled.'); 19 | } 20 | }; 21 | 22 | return { 23 | setSplashSeen, 24 | }; 25 | }; 26 | 27 | export default useSplashApi; 28 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useApiGetter/useApiGetter.ts: -------------------------------------------------------------------------------- 1 | import useSWR, { SWRConfiguration, Key } from 'swr'; 2 | import { useCallback } from 'react'; 3 | 4 | interface IUseApiGetterOutput { 5 | data?: T; 6 | refetch: () => void; 7 | error?: Error | undefined; 8 | loading: boolean; 9 | } 10 | 11 | export const useApiGetter = ( 12 | cacheKey: Key, 13 | fetcher: () => Promise, 14 | options?: SWRConfiguration 15 | ): IUseApiGetterOutput => { 16 | const { data, error, mutate } = useSWR(cacheKey, fetcher, options); 17 | 18 | const refetch = useCallback(() => { 19 | mutate().catch(console.warn); 20 | }, [mutate]); 21 | 22 | return { 23 | data, 24 | error, 25 | refetch, 26 | loading: !error && !data, 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useAuth/useAuthDetails.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IAuthEndpointDetailsResponse, 3 | useAuthEndpoint, 4 | } from './useAuthEndpoint'; 5 | 6 | interface IUseAuthDetailsOutput { 7 | authDetails?: IAuthEndpointDetailsResponse; 8 | refetchAuthDetails: () => void; 9 | loading: boolean; 10 | error?: Error; 11 | } 12 | 13 | export const useAuthDetails = (): IUseAuthDetailsOutput => { 14 | const auth = useAuthEndpoint(); 15 | const authDetails = 16 | auth.data && 'type' in auth.data ? auth.data : undefined; 17 | 18 | return { 19 | authDetails, 20 | refetchAuthDetails: auth.refetchAuth, 21 | loading: auth.loading, 22 | error: auth.error, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useAuth/useAuthFeedback.ts: -------------------------------------------------------------------------------- 1 | import { IAuthFeedback, useAuthEndpoint } from './useAuthEndpoint'; 2 | 3 | interface IUseAuthFeedbackOutput { 4 | feedback?: IAuthFeedback[]; 5 | refetchFeedback: () => void; 6 | loading: boolean; 7 | error?: Error; 8 | } 9 | 10 | export const useAuthFeedback = (): IUseAuthFeedbackOutput => { 11 | const auth = useAuthEndpoint(); 12 | const feedback = 13 | auth.data && 'feedback' in auth.data ? auth.data.feedback : undefined; 14 | 15 | return { 16 | feedback, 17 | refetchFeedback: auth.refetchAuth, 18 | loading: auth.loading, 19 | error: auth.error, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useAuth/useAuthPermissions.ts: -------------------------------------------------------------------------------- 1 | import { IPermission } from 'interfaces/user'; 2 | import { useAuthEndpoint } from './useAuthEndpoint'; 3 | 4 | interface IUseAuthPermissionsOutput { 5 | permissions?: IPermission[]; 6 | refetchPermissions: () => void; 7 | loading: boolean; 8 | error?: Error; 9 | } 10 | 11 | export const useAuthPermissions = (): IUseAuthPermissionsOutput => { 12 | const auth = useAuthEndpoint(); 13 | const permissions = 14 | auth.data && 'permissions' in auth.data 15 | ? auth.data.permissions 16 | : undefined; 17 | 18 | return { 19 | permissions, 20 | refetchPermissions: auth.refetchAuth, 21 | loading: auth.loading, 22 | error: auth.error, 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useAuth/useAuthSplash.ts: -------------------------------------------------------------------------------- 1 | import { IAuthSplash, useAuthEndpoint } from './useAuthEndpoint'; 2 | 3 | interface IUseAuthSplashOutput { 4 | splash?: IAuthSplash; 5 | refetchSplash: () => void; 6 | loading: boolean; 7 | error?: Error; 8 | } 9 | 10 | export const useAuthSplash = (): IUseAuthSplashOutput => { 11 | const auth = useAuthEndpoint(); 12 | const splash = 13 | auth.data && 'splash' in auth.data ? auth.data.splash : undefined; 14 | 15 | return { 16 | splash, 17 | refetchSplash: auth.refetchAuth, 18 | loading: auth.loading, 19 | error: auth.error, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useAuth/useAuthUser.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from 'interfaces/user'; 2 | import { useAuthEndpoint } from './useAuthEndpoint'; 3 | 4 | interface IUseAuthUserOutput { 5 | user?: IUser; 6 | refetchUser: () => void; 7 | loading: boolean; 8 | error?: Error; 9 | } 10 | 11 | export const useAuthUser = (): IUseAuthUserOutput => { 12 | const auth = useAuthEndpoint(); 13 | const user = auth.data && 'user' in auth.data ? auth.data.user : undefined; 14 | 15 | return { 16 | user, 17 | refetchUser: auth.refetchAuth, 18 | loading: auth.loading, 19 | error: auth.error, 20 | }; 21 | }; 22 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useDefaultProject/useDefaultProjectId.ts: -------------------------------------------------------------------------------- 1 | import useProjects from 'hooks/api/getters/useProjects/useProjects'; 2 | 3 | export const DEFAULT_PROJECT_ID = 'default'; 4 | 5 | // Get the default project ID, or the first ID if there is no default project. 6 | export const useDefaultProjectId = (): string | undefined => { 7 | const { projects = [] } = useProjects(); 8 | 9 | const defaultProject = projects.find(project => { 10 | return project.id === DEFAULT_PROJECT_ID; 11 | }); 12 | 13 | return defaultProject?.id || projects[0]?.id; 14 | }; 15 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useEnvironment/defaultEnvironment.ts: -------------------------------------------------------------------------------- 1 | import { IEnvironment } from 'interfaces/environments'; 2 | 3 | export const defaultEnvironment: IEnvironment = { 4 | name: '', 5 | type: '', 6 | createdAt: '', 7 | sortOrder: 0, 8 | enabled: false, 9 | protected: false, 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useFeature/emptyFeature.ts: -------------------------------------------------------------------------------- 1 | import { IFeatureToggle } from 'interfaces/featureToggle'; 2 | 3 | export const emptyFeature: IFeatureToggle = { 4 | environments: [], 5 | name: '', 6 | type: '', 7 | stale: false, 8 | archived: false, 9 | createdAt: '', 10 | lastSeenAt: '', 11 | project: '', 12 | variants: [], 13 | description: '', 14 | impressionData: false, 15 | }; 16 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useProject/getProjectFetcher.ts: -------------------------------------------------------------------------------- 1 | import { formatApiPath } from 'utils/formatPath'; 2 | import handleErrorResponses from '../httpErrorResponseHandler'; 3 | 4 | export const getProjectFetcher = (id: string) => { 5 | const fetcher = () => { 6 | const path = formatApiPath(`api/admin/projects/${id}`); 7 | return fetch(path, { 8 | method: 'GET', 9 | }) 10 | .then(handleErrorResponses('Project overview')) 11 | .then(res => res.json()); 12 | }; 13 | 14 | const KEY = `api/admin/projects/${id}`; 15 | 16 | return { 17 | fetcher, 18 | KEY, 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /src/hooks/api/getters/useSegmentLimits/useSegmentLimits.ts: -------------------------------------------------------------------------------- 1 | import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; 2 | import { IUiConfig } from 'interfaces/uiConfig'; 3 | 4 | type IUseSegmentLimits = Pick< 5 | IUiConfig, 6 | 'segmentValuesLimit' | 'strategySegmentsLimit' 7 | >; 8 | 9 | export const useSegmentLimits = (): IUseSegmentLimits => { 10 | const { uiConfig } = useUiConfig(); 11 | 12 | return { 13 | segmentValuesLimit: uiConfig.segmentValuesLimit, 14 | strategySegmentsLimit: uiConfig.strategySegmentsLimit, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/hooks/useGlobalState.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createGlobalState } from 'react-hooks-global-state'; 3 | 4 | type UseGlobalState = () => [ 5 | value: T, 6 | setValue: React.Dispatch> 7 | ]; 8 | 9 | // Create a hook that stores global state (shared across all hook instances). 10 | export const createGlobalStateHook = ( 11 | key: string, 12 | initialValue: T 13 | ): UseGlobalState => { 14 | const container = createGlobalState<{ [key: string]: T }>({ 15 | [key]: initialValue, 16 | }); 17 | 18 | const setGlobalState = (value: React.SetStateAction) => { 19 | container.setGlobalState(key, value); 20 | }; 21 | 22 | return () => [container.useGlobalState(key)[0], setGlobalState]; 23 | }; 24 | -------------------------------------------------------------------------------- /src/hooks/useHiddenColumns.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { IdType } from 'react-table'; 3 | 4 | const useHiddenColumns = ( 5 | setHiddenColumns: (param: Array>) => void, 6 | hiddenColumns: string[], 7 | condition: boolean 8 | ) => { 9 | useEffect(() => { 10 | const hidden = condition ? hiddenColumns : []; 11 | setHiddenColumns(hidden); 12 | }, [setHiddenColumns, hiddenColumns, condition]); 13 | }; 14 | 15 | export default useHiddenColumns; 16 | -------------------------------------------------------------------------------- /src/hooks/useId.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | 3 | // Generate a globally unique ID that is stable across renders. 4 | export const useId = (prefix = 'useId'): string => { 5 | return useMemo(() => { 6 | return `${prefix}-${counter++}`; 7 | }, [prefix]); 8 | }; 9 | 10 | let counter = 0; 11 | -------------------------------------------------------------------------------- /src/hooks/useIsAppleDevice.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | export const useIsAppleDevice = () => { 4 | const [isAppleDevice, setIsAppleDevice] = useState(); 5 | 6 | useEffect(() => { 7 | const platform = 8 | ( 9 | navigator as unknown as { 10 | userAgentData: { platform: string }; 11 | } 12 | )?.userAgentData?.platform || 13 | navigator?.platform || 14 | 'unknown'; 15 | 16 | setIsAppleDevice( 17 | platform.toLowerCase().includes('mac') || 18 | platform === 'iPhone' || 19 | platform === 'iPad' 20 | ); 21 | }, []); 22 | 23 | return isAppleDevice; 24 | }; 25 | -------------------------------------------------------------------------------- /src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { createRef, useLayoutEffect } from 'react'; 2 | 3 | type refElement = HTMLDivElement; 4 | 5 | const useLoading = (loading: boolean) => { 6 | const ref = createRef(); 7 | useLayoutEffect(() => { 8 | if (ref.current) { 9 | const elements = ref.current.querySelectorAll('[data-loading]'); 10 | 11 | elements.forEach(element => { 12 | if (loading) { 13 | element.classList.add('skeleton'); 14 | } else { 15 | element.classList.remove('skeleton'); 16 | } 17 | }); 18 | } 19 | }, [loading, ref]); 20 | 21 | return ref; 22 | }; 23 | 24 | export default useLoading; 25 | -------------------------------------------------------------------------------- /src/hooks/useOptionalPathParam.ts: -------------------------------------------------------------------------------- 1 | import { useParams } from 'react-router-dom'; 2 | 3 | export const useOptionalPathParam = (key: string): string | undefined => { 4 | return useParams<{ [key: string]: string | undefined }>()[key]; 5 | }; 6 | -------------------------------------------------------------------------------- /src/hooks/usePageTitle.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useContext } from 'react'; 2 | import { AnnouncerContext } from 'component/common/Announcer/AnnouncerContext/AnnouncerContext'; 3 | 4 | export const usePageTitle = (title?: string) => { 5 | const { setAnnouncement } = useContext(AnnouncerContext); 6 | 7 | useEffect(() => { 8 | if (title) { 9 | document.title = title; 10 | return () => { 11 | document.title = DEFAULT_PAGE_TITLE; 12 | }; 13 | } 14 | }, [title]); 15 | 16 | useEffect(() => { 17 | if (title && title !== DEFAULT_PAGE_TITLE) { 18 | setAnnouncement(`Navigated to ${title}`); 19 | } 20 | }, [setAnnouncement, title]); 21 | }; 22 | 23 | const DEFAULT_PAGE_TITLE = 'Unleash'; 24 | -------------------------------------------------------------------------------- /src/hooks/usePlausibleTracker.test.ts: -------------------------------------------------------------------------------- 1 | import { renderHook } from '@testing-library/react-hooks'; 2 | import { 3 | usePlausibleTracker, 4 | enablePlausibleTracker, 5 | } from 'hooks/usePlausibleTracker'; 6 | 7 | test('usePlausibleTracker', async () => { 8 | const { result } = renderHook(() => usePlausibleTracker()); 9 | expect(result.current).toBeUndefined(); 10 | }); 11 | 12 | test('enablePlausibleTracker', async () => { 13 | expect(enablePlausibleTracker({})).toEqual(false); 14 | expect(enablePlausibleTracker({ SE: true })).toEqual(false); 15 | expect(enablePlausibleTracker({ T: false })).toEqual(false); 16 | expect(enablePlausibleTracker({ T: true })).toEqual(true); 17 | }); 18 | -------------------------------------------------------------------------------- /src/hooks/useQueryParams.ts: -------------------------------------------------------------------------------- 1 | import { useLocation } from 'react-router'; 2 | 3 | const useQueryParams = () => { 4 | return new URLSearchParams(useLocation().search); 5 | }; 6 | 7 | export default useQueryParams; 8 | -------------------------------------------------------------------------------- /src/hooks/useQueryStringNumberState.ts: -------------------------------------------------------------------------------- 1 | import { useCallback } from 'react'; 2 | import { useQueryStringState } from './useQueryStringState'; 3 | 4 | // Store a number in the query string. Call setState to update the query string. 5 | export const useQueryStringNumberState = ( 6 | key: string 7 | ): [number | undefined, (value: number) => void] => { 8 | const [value, setValue] = useQueryStringState(key); 9 | 10 | const setState = useCallback( 11 | (value: number) => setValue(String(value)), 12 | [setValue] 13 | ); 14 | 15 | return [ 16 | Number.isFinite(Number(value)) ? Number(value) : undefined, 17 | setState, 18 | ]; 19 | }; 20 | -------------------------------------------------------------------------------- /src/hooks/useRequiredPathParam.ts: -------------------------------------------------------------------------------- 1 | import { useOptionalPathParam } from './useOptionalPathParam'; 2 | 3 | export const useRequiredPathParam = (key: string): string => { 4 | const value = useOptionalPathParam(key); 5 | 6 | if (!value) { 7 | throw new Error(`Missing required path param: ${key}`); 8 | } 9 | 10 | return value; 11 | }; 12 | -------------------------------------------------------------------------------- /src/hooks/useRequiredQueryParam.ts: -------------------------------------------------------------------------------- 1 | export const useRequiredQueryParam = (key: string) => { 2 | const value = new URLSearchParams(window.location.search).get(key); 3 | 4 | if (!value) { 5 | throw new Error(`Missing required query param: ${key}`); 6 | } 7 | 8 | return value; 9 | }; 10 | -------------------------------------------------------------------------------- /src/hooks/useTabs.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const useTabs = (startingIndex: number = 0) => { 4 | const [activeTabIdx, setActiveTab] = useState(startingIndex); 5 | 6 | const a11yProps = (index: number) => ({ 7 | id: `tab-${index}`, 8 | 'aria-controls': `tabpanel-${index}`, 9 | }); 10 | 11 | return { activeTabIdx, setActiveTab, a11yProps }; 12 | }; 13 | 14 | export default useTabs; 15 | -------------------------------------------------------------------------------- /src/interfaces/application.ts: -------------------------------------------------------------------------------- 1 | export interface IApplication { 2 | appName: string; 3 | color: string; 4 | createdAt: string; 5 | description: string; 6 | icon: string; 7 | instances: []; 8 | links: object; 9 | seenToggles: []; 10 | strategies: []; 11 | url: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/interfaces/context.ts: -------------------------------------------------------------------------------- 1 | export interface IUnleashContextDefinition { 2 | name: string; 3 | description: string; 4 | createdAt: string; 5 | sortOrder: number; 6 | stickiness: boolean; 7 | legalValues?: ILegalValue[]; 8 | } 9 | 10 | export interface ILegalValue { 11 | value: string; 12 | description?: string; 13 | } 14 | -------------------------------------------------------------------------------- /src/interfaces/environments.ts: -------------------------------------------------------------------------------- 1 | export interface IEnvironment { 2 | name: string; 3 | type: string; 4 | createdAt: string; 5 | sortOrder: number; 6 | enabled: boolean; 7 | protected: boolean; 8 | } 9 | 10 | export interface IProjectEnvironment { 11 | enabled: boolean; 12 | name: string; 13 | } 14 | 15 | export interface IEnvironmentPayload { 16 | name: string; 17 | type: string; 18 | } 19 | 20 | export interface IEnvironmentEditPayload { 21 | sortOrder: number; 22 | type: string; 23 | } 24 | 25 | export interface IEnvironmentResponse { 26 | environments: IEnvironment[]; 27 | } 28 | 29 | export interface ISortOrderPayload { 30 | [index: string]: number; 31 | } 32 | -------------------------------------------------------------------------------- /src/interfaces/event.ts: -------------------------------------------------------------------------------- 1 | import { ITag } from './tags'; 2 | 3 | export interface IEvent { 4 | id: number; 5 | createdAt: string; 6 | type: string; 7 | createdBy: string; 8 | project?: string; 9 | environment?: string; 10 | featureName?: string; 11 | data?: any; 12 | preData?: any; 13 | tags?: ITag[]; 14 | } 15 | -------------------------------------------------------------------------------- /src/interfaces/featureTypes.ts: -------------------------------------------------------------------------------- 1 | export interface IFeatureType { 2 | id: string; 3 | name: string; 4 | description: string; 5 | lifetimeDays: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/interfaces/group.ts: -------------------------------------------------------------------------------- 1 | import { IUser } from './user'; 2 | 3 | export interface IGroup { 4 | id: number; 5 | name: string; 6 | description: string; 7 | createdAt: Date; 8 | users: IGroupUser[]; 9 | projects: string[]; 10 | addedAt?: string; 11 | userCount?: number; 12 | } 13 | 14 | export interface IGroupUser extends IUser { 15 | joinedAt?: Date; 16 | } 17 | 18 | export interface IGroupUserModel { 19 | user: { 20 | id: number; 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/interfaces/instance.ts: -------------------------------------------------------------------------------- 1 | export interface IInstanceStatus { 2 | plan: InstancePlan; 3 | trialExpiry?: string; 4 | trialStart?: string; 5 | trialExtended?: number; 6 | billingCenter?: string; 7 | state?: InstanceState; 8 | seats?: number; 9 | } 10 | 11 | export enum InstanceState { 12 | UNASSIGNED = 'UNASSIGNED', 13 | TRIAL = 'TRIAL', 14 | ACTIVE = 'ACTIVE', 15 | EXPIRED = 'EXPIRED', 16 | CHURNED = 'CHURNED', 17 | } 18 | 19 | export enum InstancePlan { 20 | PRO = 'Pro', 21 | COMPANY = 'Company', 22 | TEAM = 'Team', 23 | UNKNOWN = 'Unknown', 24 | } 25 | -------------------------------------------------------------------------------- /src/interfaces/invoice.ts: -------------------------------------------------------------------------------- 1 | export interface IInvoice { 2 | amountFormatted: string; 3 | invoicePDF: string; 4 | invoiceURL: string; 5 | paid: boolean; 6 | status: string; 7 | dueDate?: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/interfaces/role.ts: -------------------------------------------------------------------------------- 1 | interface IRole { 2 | id: number; 3 | name: string; 4 | project: string | null; 5 | description: string; 6 | type: string; 7 | } 8 | 9 | export interface IProjectRole { 10 | id: number; 11 | name: string; 12 | description: string; 13 | type: string; 14 | } 15 | 16 | export default IRole; 17 | -------------------------------------------------------------------------------- /src/interfaces/route.ts: -------------------------------------------------------------------------------- 1 | import { VoidFunctionComponent } from 'react'; 2 | import { IUiConfig } from 'interfaces/uiConfig'; 3 | 4 | export interface IRoute { 5 | path: string; 6 | title: string; 7 | type: 'protected' | 'unprotected'; 8 | layout?: string; 9 | parent?: string; 10 | flag?: string; 11 | configFlag?: keyof IUiConfig; 12 | hidden?: boolean; 13 | enterprise?: boolean; 14 | component: VoidFunctionComponent; 15 | menu: IRouteMenu; 16 | } 17 | 18 | interface IRouteMenu { 19 | mobile?: boolean; 20 | advanced?: boolean; 21 | adminSettings?: boolean; 22 | isEnterprise?: boolean; 23 | } 24 | -------------------------------------------------------------------------------- /src/interfaces/segment.ts: -------------------------------------------------------------------------------- 1 | import { IConstraint } from './strategy'; 2 | 3 | export interface ISegment { 4 | id: number; 5 | name: string; 6 | description: string; 7 | createdAt: string; 8 | createdBy: string; 9 | constraints: IConstraint[]; 10 | } 11 | 12 | export type ISegmentPayload = Pick< 13 | ISegment, 14 | 'name' | 'description' | 'constraints' 15 | >; 16 | -------------------------------------------------------------------------------- /src/interfaces/tags.ts: -------------------------------------------------------------------------------- 1 | export interface ITag { 2 | value: string; 3 | type: string; 4 | } 5 | 6 | export interface ITagType { 7 | name: string; 8 | description: string; 9 | icon: string; 10 | } 11 | 12 | export interface ITagPayload { 13 | name: string; 14 | description: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/interfaces/toast.ts: -------------------------------------------------------------------------------- 1 | export interface IToast { 2 | type: 'success' | 'error'; 3 | title: string; 4 | text?: string; 5 | components?: JSX.Element[]; 6 | show?: boolean; 7 | persist?: boolean; 8 | confetti?: boolean; 9 | autoHideDuration?: number; 10 | } 11 | -------------------------------------------------------------------------------- /src/interfaces/token.ts: -------------------------------------------------------------------------------- 1 | export enum TokenType { 2 | ADMIN = 'ADMIN', 3 | CLIENT = 'CLIENT', 4 | FRONTEND = 'FRONTEND', 5 | } 6 | -------------------------------------------------------------------------------- /src/interfaces/user.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | id: number; 3 | email: string; 4 | name: string; 5 | createdAt: string; 6 | imageUrl: string; 7 | loginAttempts: number; 8 | permissions: string[] | null; 9 | inviteLink: string; 10 | rootRole: number; 11 | seenAt: string | null; 12 | username?: string; 13 | isAPI: boolean; 14 | paid?: boolean; 15 | addedAt?: string; 16 | } 17 | 18 | export interface IPermission { 19 | permission: string; 20 | project?: string; 21 | environment?: string; 22 | } 23 | -------------------------------------------------------------------------------- /src/openapi/apis/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './AdminApi'; 4 | -------------------------------------------------------------------------------- /src/openapi/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './runtime'; 4 | export * from './apis'; 5 | export * from './models'; 6 | -------------------------------------------------------------------------------- /src/openapi/models/index.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | export * from './CloneFeatureSchema'; 4 | export * from './ConstraintSchema'; 5 | export * from './CreateFeatureSchema'; 6 | export * from './CreateStrategySchema'; 7 | export * from './FeatureEnvironmentSchema'; 8 | export * from './FeatureSchema'; 9 | export * from './FeatureStrategySchema'; 10 | export * from './FeaturesSchema'; 11 | export * from './OverrideSchema'; 12 | export * from './PatchOperationSchema'; 13 | export * from './StrategySchema'; 14 | export * from './TagSchema'; 15 | export * from './TagsResponseSchema'; 16 | export * from './UpdateFeatureSchema'; 17 | export * from './UpdateStrategySchema'; 18 | export * from './VariantSchema'; 19 | export * from './VariantSchemaPayload'; 20 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import 'whatwg-fetch'; 3 | import 'regenerator-runtime'; 4 | 5 | process.env.TZ = 'UTC'; 6 | -------------------------------------------------------------------------------- /src/themes/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from 'react'; 2 | import { CssBaseline, ThemeProvider as MuiThemeProvider } from '@mui/material'; 3 | 4 | import createCache from '@emotion/cache'; 5 | import { CacheProvider } from '@emotion/react'; 6 | import { useThemeMode } from 'hooks/useThemeMode'; 7 | 8 | export const muiCache = createCache({ 9 | key: 'mui', 10 | prepend: true, 11 | }); 12 | 13 | export const ThemeProvider: FC = ({ children }) => { 14 | const { resolveTheme } = useThemeMode(); 15 | 16 | return ( 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/types/global-fetch.d.ts: -------------------------------------------------------------------------------- 1 | // Add missing GlobalFetch declaration for the OpenAPI bindings. 2 | // https://github.com/apollographql/apollo-link/issues/1131#issuecomment-526109609 3 | declare type GlobalFetch = WindowOrWorkerGlobalScope; 4 | -------------------------------------------------------------------------------- /src/utils/arraysHaveSameItems.ts: -------------------------------------------------------------------------------- 1 | export const arraysHaveSameItems = (a: T[], b: T[]): boolean => { 2 | const setA = new Set(a); 3 | const setB = new Set(b); 4 | 5 | if (setA.size !== setB.size) { 6 | return false; 7 | } 8 | 9 | return [...setA].every(itemA => { 10 | return setB.has(itemA); 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/calculatePercentage.ts: -------------------------------------------------------------------------------- 1 | export const calculatePercentage = (total: number, part: number) => { 2 | if (total === 0) { 3 | return 0; 4 | } 5 | 6 | return Math.round((part / total) * 100); 7 | }; 8 | -------------------------------------------------------------------------------- /src/utils/cleanConstraint.test.ts: -------------------------------------------------------------------------------- 1 | import { cleanConstraint } from 'utils/cleanConstraint'; 2 | 3 | test('cleanConstraint values', () => { 4 | expect( 5 | cleanConstraint({ 6 | contextName: '', 7 | operator: 'IN', 8 | value: '1', 9 | values: ['2'], 10 | }) 11 | ).toEqual({ 12 | contextName: '', 13 | operator: 'IN', 14 | values: ['2'], 15 | }); 16 | }); 17 | 18 | test('cleanConstraint value', () => { 19 | expect( 20 | cleanConstraint({ 21 | contextName: '', 22 | operator: 'NUM_EQ', 23 | value: '1', 24 | values: ['2'], 25 | }) 26 | ).toEqual({ 27 | contextName: '', 28 | operator: 'NUM_EQ', 29 | value: '1', 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/utils/cleanConstraint.ts: -------------------------------------------------------------------------------- 1 | import { singleValueOperators } from 'constants/operators'; 2 | import { IConstraint } from 'interfaces/strategy'; 3 | import { oneOf } from 'utils/oneOf'; 4 | import produce from 'immer'; 5 | 6 | export const cleanConstraint = ( 7 | constraint: Readonly 8 | ): IConstraint => { 9 | return produce(constraint, draft => { 10 | if (oneOf(singleValueOperators, constraint.operator)) { 11 | delete draft.values; 12 | } else { 13 | delete draft.value; 14 | } 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/env.ts: -------------------------------------------------------------------------------- 1 | export const isLocalhostDomain = (hostname = window.location.hostname) => 2 | hostname === 'localhost'; 3 | 4 | export const isUnleashDomain = (hostname = window.location.hostname) => 5 | hostname.endsWith('.getunleash.io') || 6 | hostname.endsWith('.unleash-hosted.com'); 7 | 8 | export const isVercelBranchDomain = (hostname = window.location.hostname) => 9 | hostname.endsWith('.vercel.app'); 10 | -------------------------------------------------------------------------------- /src/utils/formatAccessText.test.ts: -------------------------------------------------------------------------------- 1 | import { formatAccessText } from 'utils/formatAccessText'; 2 | 3 | test('formatAccessText with access', () => { 4 | expect(formatAccessText(true)).toEqual(undefined); 5 | expect(formatAccessText(true, 'Foo')).toEqual('Foo'); 6 | }); 7 | 8 | test('formatAccessText without access', () => { 9 | expect(formatAccessText(false)).toEqual('Access denied'); 10 | expect(formatAccessText(false, 'Foo')).toEqual(`Foo (Access denied)`); 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/formatAccessText.ts: -------------------------------------------------------------------------------- 1 | export const ACCESS_DENIED_TEXT = 'Access denied'; 2 | 3 | export const formatAccessText = ( 4 | hasAccess: boolean, 5 | hasAccessText?: string 6 | ): string | undefined => { 7 | if (hasAccess) { 8 | return hasAccessText; 9 | } 10 | 11 | if (hasAccessText) { 12 | return `${hasAccessText} (${ACCESS_DENIED_TEXT})`; 13 | } 14 | 15 | return ACCESS_DENIED_TEXT; 16 | }; 17 | -------------------------------------------------------------------------------- /src/utils/formatConstraintValue.ts: -------------------------------------------------------------------------------- 1 | import { IConstraint } from 'interfaces/strategy'; 2 | import { formatDateYMDHMS } from 'utils/formatDate'; 3 | import { ILocationSettings } from 'hooks/useLocationSettings'; 4 | import { CURRENT_TIME_CONTEXT_FIELD } from 'utils/operatorsForContext'; 5 | 6 | export const formatConstraintValue = ( 7 | constraint: IConstraint, 8 | locationSettings: ILocationSettings 9 | ): string | undefined => { 10 | if ( 11 | constraint.value && 12 | constraint.contextName === CURRENT_TIME_CONTEXT_FIELD 13 | ) { 14 | return formatDateYMDHMS(constraint.value, locationSettings.locale); 15 | } 16 | 17 | return constraint.value; 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/formatUnknownError.test.ts: -------------------------------------------------------------------------------- 1 | import { formatUnknownError } from 'utils/formatUnknownError'; 2 | 3 | test('formatUnknownError', () => { 4 | expect(formatUnknownError(1)).toEqual('Unknown error'); 5 | expect(formatUnknownError('1')).toEqual('1'); 6 | expect(formatUnknownError(new Error('1'))).toEqual('1'); 7 | expect(formatUnknownError(new Error())).toEqual('Error'); 8 | }); 9 | -------------------------------------------------------------------------------- /src/utils/formatUnknownError.ts: -------------------------------------------------------------------------------- 1 | // Get a human-readable error message string from a caught value. 2 | export const formatUnknownError = (error: unknown): string => { 3 | if (error instanceof Error) { 4 | return error.message || error.toString(); 5 | } else if (typeof error === 'string') { 6 | return error; 7 | } else { 8 | return 'Unknown error'; 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /src/utils/getFeatureMetrics.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IFeatureEnvironment, 3 | IFeatureMetrics, 4 | } from '../interfaces/featureToggle'; 5 | 6 | const emptyMetric = (environment: string) => ({ 7 | yes: 0, 8 | no: 0, 9 | environment, 10 | timestamp: '', 11 | }); 12 | 13 | export const getFeatureMetrics = ( 14 | environments: IFeatureEnvironment[], 15 | metrics: IFeatureMetrics 16 | ) => { 17 | return environments.map(env => { 18 | const envMetric = metrics.lastHourUsage.find( 19 | metric => metric.environment === env.name 20 | ); 21 | return envMetric || emptyMetric(env.name); 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/nonEmptyArray.ts: -------------------------------------------------------------------------------- 1 | export const nonEmptyArray = (value: unknown): boolean => { 2 | return Boolean(value) && Array.isArray(value) && value.length > 0; 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/objectId.test.ts: -------------------------------------------------------------------------------- 1 | import { objectId } from 'utils/objectId'; 2 | 3 | test('objectId', () => { 4 | const a = {}; 5 | const b = {}; 6 | const c = {}; 7 | expect(objectId(a)).toBeGreaterThan(0); 8 | expect(objectId(b)).toBeGreaterThan(0); 9 | expect(objectId(c)).toBeGreaterThan(0); 10 | expect(objectId(a)).toBe(objectId(a)); 11 | expect(objectId(a)).not.toBe(objectId(b)); 12 | expect(objectId(a)).not.toBe(objectId(c)); 13 | }); 14 | -------------------------------------------------------------------------------- /src/utils/objectId.ts: -------------------------------------------------------------------------------- 1 | // Get a unique ID for an object instance. 2 | export const objectId = (value: object): number => { 3 | if (!ids.has(value)) { 4 | id++; 5 | ids.set(value, id); 6 | } 7 | 8 | return ids.get(value)!; 9 | }; 10 | 11 | const ids = new WeakMap(); 12 | let id = 0; 13 | -------------------------------------------------------------------------------- /src/utils/oneOf.ts: -------------------------------------------------------------------------------- 1 | export const oneOf = (values: string[], match: string) => { 2 | return values.some(value => value === match); 3 | }; 4 | -------------------------------------------------------------------------------- /src/utils/openapiClient.ts: -------------------------------------------------------------------------------- 1 | import { Configuration, AdminApi } from 'openapi'; 2 | import { basePath } from 'utils/formatPath'; 3 | 4 | const createAdminApi = (): AdminApi => { 5 | return new AdminApi( 6 | new Configuration({ 7 | basePath, 8 | }) 9 | ); 10 | }; 11 | 12 | export const openApiAdmin = createAdminApi(); 13 | -------------------------------------------------------------------------------- /src/utils/operatorsForContext.ts: -------------------------------------------------------------------------------- 1 | import { allOperators, dateOperators, Operator } from 'constants/operators'; 2 | import { oneOf } from 'utils/oneOf'; 3 | 4 | export const CURRENT_TIME_CONTEXT_FIELD = 'currentTime'; 5 | 6 | export const operatorsForContext = (contextName: string): Operator[] => { 7 | return allOperators.filter(operator => { 8 | if ( 9 | oneOf(dateOperators, operator) && 10 | contextName !== CURRENT_TIME_CONTEXT_FIELD 11 | ) { 12 | return false; 13 | } 14 | 15 | if ( 16 | !oneOf(dateOperators, operator) && 17 | contextName === CURRENT_TIME_CONTEXT_FIELD 18 | ) { 19 | return false; 20 | } 21 | 22 | return true; 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /src/utils/paginate.ts: -------------------------------------------------------------------------------- 1 | export const paginate = (data: any[], limit: number) => { 2 | let result = []; 3 | let currentIdx = 0; 4 | 5 | if (data.length <= currentIdx) { 6 | return data; 7 | } 8 | 9 | while (currentIdx < data.length) { 10 | if (currentIdx === 0) { 11 | currentIdx += limit; 12 | const page = data.slice(0, currentIdx); 13 | result.push(page); 14 | } else { 15 | const page = data.slice(currentIdx, currentIdx + limit); 16 | currentIdx += limit; 17 | result.push(page); 18 | } 19 | } 20 | 21 | return result; 22 | }; 23 | -------------------------------------------------------------------------------- /src/utils/parseParameter.ts: -------------------------------------------------------------------------------- 1 | import { IFeatureStrategyParameters } from 'interfaces/strategy'; 2 | 3 | export const parseParameterNumber = ( 4 | value: IFeatureStrategyParameters[string] 5 | ): number => { 6 | const parsed = Number(parseParameterString(value)); 7 | return Number.isFinite(parsed) ? parsed : 0; 8 | }; 9 | 10 | export const parseParameterString = ( 11 | value: IFeatureStrategyParameters[string] 12 | ): string => { 13 | return String(value ?? '').trim(); 14 | }; 15 | 16 | export const parseParameterStrings = ( 17 | value: IFeatureStrategyParameters[string] 18 | ): string[] => { 19 | return parseParameterString(value) 20 | .split(',') 21 | .map(s => s.trim()) 22 | .filter(Boolean); 23 | }; 24 | -------------------------------------------------------------------------------- /src/utils/removeEmptyStringFields.test.ts: -------------------------------------------------------------------------------- 1 | import { removeEmptyStringFields } from 'utils/removeEmptyStringFields'; 2 | 3 | test('removeEmptyStringFields', () => { 4 | expect(removeEmptyStringFields({})).toEqual({}); 5 | expect(removeEmptyStringFields({ a: undefined })).toEqual({ a: undefined }); 6 | expect(removeEmptyStringFields({ a: 0 })).toEqual({ a: 0 }); 7 | expect(removeEmptyStringFields({ a: 1 })).toEqual({ a: 1 }); 8 | expect(removeEmptyStringFields({ a: '1' })).toEqual({ a: '1' }); 9 | expect(removeEmptyStringFields({ a: '' })).toEqual({}); 10 | expect(removeEmptyStringFields({ a: '', b: '2' })).toEqual({ b: '2' }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/utils/removeEmptyStringFields.ts: -------------------------------------------------------------------------------- 1 | // Remove fields from an object if their value is the empty string. 2 | export const removeEmptyStringFields = (object: { 3 | [key: string]: unknown; 4 | }): { [key: string]: unknown } => { 5 | const entries = Object.entries(object); 6 | const filtered = entries.filter(([, v]) => v !== ''); 7 | 8 | return Object.fromEntries(filtered); 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/testServer.ts: -------------------------------------------------------------------------------- 1 | import { SetupServerApi, setupServer } from 'msw/node'; 2 | import { rest } from 'msw'; 3 | 4 | export const testServerSetup = (): SetupServerApi => { 5 | const server = setupServer(); 6 | 7 | beforeAll(() => server.listen()); 8 | afterAll(() => server.close()); 9 | afterEach(() => server.resetHandlers()); 10 | 11 | return server; 12 | }; 13 | 14 | export const testServerRoute = ( 15 | server: SetupServerApi, 16 | path: string, 17 | json: object 18 | ) => { 19 | server.use( 20 | rest.get(path, (req, res, ctx) => { 21 | return res(ctx.json(json)); 22 | }) 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src", 4 | "target": "esnext", 5 | "lib": [ 6 | "dom", 7 | "dom.iterable", 8 | "esnext" 9 | ], 10 | "allowJs": true, 11 | "skipLibCheck": true, 12 | "esModuleInterop": true, 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | "strict": true 23 | }, 24 | "include": [ 25 | "src" 26 | ], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [ 3 | { 4 | "source": "/api/:match*", 5 | "destination": "https://unleash4.herokuapp.com/api/:match*" 6 | }, 7 | { 8 | "source": "/logout", 9 | "destination": "https://unleash4.herokuapp.com/logout" 10 | }, 11 | { 12 | "source": "/auth/:match*", 13 | "destination": "https://unleash4.herokuapp.com/auth/:match*" 14 | }, 15 | { "source": "/(.*)", "destination": "/index.html" } 16 | ] 17 | } 18 | --------------------------------------------------------------------------------