diff --git a/src/features/common/components/card-tabs/card-tabs.component.tsx b/src/features/common/components/card-tabs/card-tabs.component.tsx index ac6e3ae2..00af36ee 100644 --- a/src/features/common/components/card-tabs/card-tabs.component.tsx +++ b/src/features/common/components/card-tabs/card-tabs.component.tsx @@ -21,6 +21,7 @@ import { TabPanel, Tabs, } from "react-aria-components"; +import { Spinner } from "../spinner/spinner.component"; type CardTabsProps = { resizeId: string; @@ -28,6 +29,7 @@ type CardTabsProps = { title: string | null; cards: CardComponentProps[]; activeTabId: string; + isLoading?: boolean; handleActiveCardChange: (tabId: string) => void; }; @@ -35,17 +37,18 @@ const CardTabs: React.FC = ({ resizeId, title, cards, + isLoading, activeTabId, handleActiveCardChange, }) => { const tabsId = useId(); const outputModalState$ = useDebuggerStore( - (state) => state.outputModalState$, + (state) => state.outputModalState$ ); const outputModalId$ = useDebuggerStore((state) => state.outputModalId$); const closeOutputModal$ = useDebuggerStore( - (state) => state.closeOutputModal$, + (state) => state.closeOutputModal$ ); const activeCard = cards.filter((card) => card.id === activeTabId)[0]; @@ -166,9 +169,12 @@ const CardTabs: React.FC = ({ className={styles.cardTabs__container} > {title && ( -

- {title} -

+
+

+ {title} +

+ {isLoading && } +
)} void; }; @@ -281,6 +288,7 @@ export const CardTabsWithTabPersistenceComponentProps: React.FC< languageCode, title, cards, + isLoading, handleTabChange, }) => { const [activeTabId, setActiveTabId] = useState(initialTabId); @@ -309,6 +317,7 @@ export const CardTabsWithTabPersistenceComponentProps: React.FC< languageCode={languageCode} title={title} cards={cards} + isLoading={isLoading} activeTabId={activeTabId} handleActiveCardChange={handleActiveCardChange} /> diff --git a/src/features/common/components/card-tabs/card-tabs.module.scss b/src/features/common/components/card-tabs/card-tabs.module.scss index 6878b6e7..8e7a0c44 100644 --- a/src/features/common/components/card-tabs/card-tabs.module.scss +++ b/src/features/common/components/card-tabs/card-tabs.module.scss @@ -24,6 +24,7 @@ line-height: 1.375rem; font-weight: 500; letter-spacing: 0.24px; + margin-right: 8px; } .cardTabs { @@ -49,6 +50,11 @@ $cardTabs__tabList__height: 2.5rem; flex-shrink: 0; } +.cardTabs__title__container { + display: flex; + +} + .cardTab__title { position: relative; display: flex; diff --git a/src/features/common/components/spinner/spinner.component.tsx b/src/features/common/components/spinner/spinner.component.tsx new file mode 100644 index 00000000..b844cbd1 --- /dev/null +++ b/src/features/common/components/spinner/spinner.component.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { ProgressBar } from "react-aria-components"; +import styles from "./spinner.module.scss"; + +export const Spinner = () => { + return ( + + {() => ( +
+ + + +
+ )} +
+ ); +}; diff --git a/src/features/common/components/spinner/spinner.module.scss b/src/features/common/components/spinner/spinner.module.scss new file mode 100644 index 00000000..af366eb9 --- /dev/null +++ b/src/features/common/components/spinner/spinner.module.scss @@ -0,0 +1,40 @@ +.circular__spinner { + width: 16px; + height: 16px; + display: inline-block; +} + +.spinner__svg { + animation: rotate 1s linear infinite; + width: 100%; + height: 100%; +} + +.spinner__circle { + stroke: var(--color_fg_default); + stroke-dasharray: 90 150; + stroke-dashoffset: 0; + stroke-linecap: round; + animation: dash 1.5s ease-in-out infinite; +} + +@keyframes rotate { + 100% { + transform: rotate(360deg); + } +} + +@keyframes dash { + 0% { + stroke-dasharray: 1, 200; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 100, 200; + stroke-dashoffset: -15; + } + 100% { + stroke-dasharray: 1, 200; + stroke-dashoffset: -126; + } +} \ No newline at end of file diff --git a/src/features/decoder/components/decoded-header-output.component.tsx b/src/features/decoder/components/decoded-header-output.component.tsx index bb8d2e4a..653b67eb 100644 --- a/src/features/decoder/components/decoded-header-output.component.tsx +++ b/src/features/decoder/components/decoded-header-output.component.tsx @@ -38,6 +38,7 @@ export const DecodedHeaderOutputComponent: React.FC< ); const decodedHeader$ = useDecoderStore((state) => state.decodedHeader); + const isLoading = useDecoderStore((state) => state.isLoading) const cards: CardComponentProps[] = [ { @@ -105,6 +106,7 @@ export const DecodedHeaderOutputComponent: React.FC< initialTabId={decodedHeaderInitialTabId} languageCode={languageCode} title={dictionary.title} + isLoading={isLoading} cards={cards} handleTabChange={setDecodedHeaderTabValue$} /> diff --git a/src/features/decoder/services/decoder.store.ts b/src/features/decoder/services/decoder.store.ts index f0aef337..9eb5da52 100644 --- a/src/features/decoder/services/decoder.store.ts +++ b/src/features/decoder/services/decoder.store.ts @@ -48,6 +48,7 @@ export type DecoderStoreState = { asymmetricPublicKeyFormat: AsymmetricKeyFormatValues; decodedHeader: string; decodedPayload: string; + isLoading: boolean; signatureStatus: JwtSignatureStatusValues; controlledSymmetricSecretKey: { id: number; @@ -70,11 +71,11 @@ type DecoderStoreActions = { handleJwtChange: (newToken: string) => void; handleSymmetricSecretKeyChange: (newSymmetricSecretKey: string) => void; handleSymmetricSecretKeyEncodingChange: ( - newSymmetricSecretKey: EncodingValues, + newSymmetricSecretKey: EncodingValues ) => void; handleAsymmetricPublicKeyChange: (newAsymmetricPublicKey: string) => void; handleAsymmetricPublicKeyFormatChange: ( - newFormat: AsymmetricKeyFormatValues, + newFormat: AsymmetricKeyFormatValues ) => void; resetControlledSymmetricSecretKey: () => void; resetControlledAsymmetricPublicKey: () => void; @@ -92,6 +93,7 @@ export const initialState: DecoderStoreState = { asymmetricPublicKeyFormat: AsymmetricKeyFormatValues.PEM, decodedHeader: DEFAULT_DECODED_HEADER, decodedPayload: DEFAULT_DECODED_PAYLOAD, + isLoading: false, signatureStatus: JwtSignatureStatusValues.VALID, signatureWarnings: null, decodingErrors: null, @@ -120,6 +122,12 @@ export const useDecoderStore = create()( asymmetricPublicKeyFormat, } = get(); + set({ + isLoading: true, + decodedHeader: "", + decodedPayload: "", + }); + const update = await TokenDecoderService.handleJwtChange({ alg, symmetricSecretKey, @@ -129,7 +137,10 @@ export const useDecoderStore = create()( newToken, }); - set(update); + set({ + ...update, + isLoading: false, + }); }, handleSymmetricSecretKeyChange: async (newSymmetricSecretKey) => { const { jwt, symmetricSecretKeyEncoding } = get(); @@ -208,5 +219,5 @@ export const useDecoderStore = create()( set(update); }, - })), + })) );