diff --git a/README.md b/README.md index ecc2f8f..efddab5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# @zitadel/react Example +# @zitadel/react Quickstart SPA +

+ Zitadel Logo +

Authenticate your [ZITADEL](https://zitadel.com) users within your React applications. @@ -71,3 +74,49 @@ See the section about [deployment](https://facebook.github.io/create-react-app/d You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). To learn React, check out the [React documentation](https://reactjs.org/). + +## Setup Instructions + +1. **Log into the Zitadel Customer Portal** + Go to the [Zitadel Customer Portal](https://zitadel.com/admin/dashboard) + +2. **Create an Instance** + - Choose a region and create your new instance. + +3. **Create a Project** + - Inside your instance, go to Projects -> Create New Project + +![App Screenshot](assets/new_project.png) + +4. **Create an Application** + - New **"User Agent"** + - Select **"PKCE"** + - Enable **"Development Mode"** + - Configure your **Redirect URI** and **Post Logout URI** (`http://localhost:3000` for local testing) + - Click **"Continue"** and **"Create"** + - Copy the **"Client ID"** + +![App Screenshot](assets/new_application.png) + +5. **Change your token type to `JWT` in your application settings** + +![App Screenshot](assets/token_settings.png) + +6. **Configure `ZitadelConfig`** +Open `src/App.tsx` and replace the placeholder values with your application info: + +```js +const config: ZitadelConfig = { + authority: "https://CUSTOM_DOMAIN", + client_id: "YOUR_CLIENT_ID", + redirect_uri: "http://localhost:3000/callback", + post_logout_redirect_uri: "http://localhost:3000", + response_type: 'code', + scope: 'openid profile email' +}; +``` + +7. **Run the application** +```bash +yarn start +``` diff --git a/assets/new_application.png b/assets/new_application.png new file mode 100644 index 0000000..d227121 Binary files /dev/null and b/assets/new_application.png differ diff --git a/assets/new_project.png b/assets/new_project.png new file mode 100644 index 0000000..71da1d7 Binary files /dev/null and b/assets/new_project.png differ diff --git a/assets/token_settings.png b/assets/token_settings.png new file mode 100644 index 0000000..2a2956a Binary files /dev/null and b/assets/token_settings.png differ diff --git a/package.json b/package.json index 711fdd9..2a177cc 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,14 @@ "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", "@zitadel/react": "link:./lib", + "bootstrap": "^5.3.8", + "oidc-client-ts": "^2.4.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.21.3", "react-scripts": "5.0.1", "typescript": "^4.4.2", - "web-vitals": "^2.1.0", - "oidc-client-ts": "^2.4.0" + "web-vitals": "^2.1.0" }, "scripts": { "start": "react-scripts start", diff --git a/public/index.html b/public/index.html index aa069f2..376795e 100644 --- a/public/index.html +++ b/public/index.html @@ -1,43 +1,20 @@ - - - - - - - - - - - React App - - - -
- - - + \ No newline at end of file diff --git a/src/App.css b/src/App.css index 65c9b1e..fb8b4d3 100644 --- a/src/App.css +++ b/src/App.css @@ -1,56 +1,43 @@ -.App { - text-align: center; +body { + font-family: 'Lato', sans-serif; } -.App-logo { - height: 40vmin; - pointer-events: none; +.login-btn { + background-color: #5469D4; + border-color: #5469D4; + color: white; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +.logout-btn { + background-color:white; + border-color:#d4dae0; + color: #CD3D56; } -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; +.login-btn:hover, +.login-btn:focus, +.login-btn:active { + background-color: #5469D4 !important; + border-color: #5469D4 !important; + color: white !important; } -.App-link { - color: #61dafb; +.logout-btn:hover, +.logout-btn:focus, +.logout-btn:active { + background-color: white !important; + border-color: #d4dae0 !important; + color: #CD3D56 !important; } -button { - padding: 0.5rem 1rem; - background: white; - color: black; - border-radius: 0.5rem; - font-weight: 500; - border: 1px solid #333333; -} - -.user { - font-size: 1rem; -} - -.user .description { - font-size: 1rem; - margin-bottom: 1rem; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} +.jwt-box { + font-family: 'Courier New', Courier, monospace; + white-space: pre-wrap; + background-color: #f8f9fa; + /* Bootstrap light */ + padding: 1rem; + border-radius: 0.5rem; + border: 1px solid #dee2e6; + max-width: 100%; + overflow-x: auto; +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 5bf6da1..c82b532 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,22 @@ -import React, { useEffect, useState } from "react"; -import logo from "./logo.svg"; +import { useEffect, useState } from "react"; import "./App.css"; import { createZitadelAuth, ZitadelConfig } from "@zitadel/react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Navbar from "./components/Navbar"; import Login from "./components/Login"; import Callback from "./components/Callback"; +import JWTContainer from "./components/JWTContainer"; function App() { + const config: ZitadelConfig = { - authority: "", - client_id: "", + authority: "https://CUSTOM_DOMAIN", + client_id: "YOUR_CLIENT_ID", + redirect_uri: "http://localhost:3000/callback", + post_logout_redirect_uri: "http://localhost:3000", + response_type: 'code', + scope: 'openid profile email' }; const zitadel = createZitadelAuth(config); @@ -19,18 +25,24 @@ function App() { zitadel.authorize(); } - function signout() { + function logout() { zitadel.signout(); } const [authenticated, setAuthenticated] = useState(null); + const [accessToken, setAccessToken] = useState(null); + const [idToken, setIdToken] = useState(null); useEffect(() => { zitadel.userManager.getUser().then((user) => { if (user) { setAuthenticated(true); + setAccessToken(user.access_token ?? null); + setIdToken(user.id_token ?? null); } else { setAuthenticated(false); + setAccessToken(null); + setIdToken(null); } }); }, [zitadel]); @@ -38,27 +50,14 @@ function App() { return (
- logo -

Welcome to ZITADEL React

- + + + - - } - /> - } + element={} /> diff --git a/src/components/Callback.tsx b/src/components/Callback.tsx index ac13cbd..acf0997 100644 --- a/src/components/Callback.tsx +++ b/src/components/Callback.tsx @@ -1,77 +1,40 @@ -import { useEffect, useState } from "react"; -import { UserManager, User } from "oidc-client-ts"; +import { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { UserManager } from "oidc-client-ts"; type Props = { - authenticated: boolean | null; setAuth: (authenticated: boolean | null) => void; userManager: UserManager; - handleLogout: any; }; -const Callback = ({ - authenticated, - setAuth, - userManager, - - handleLogout, -}: Props) => { - const [userInfo, setUserInfo] = useState(null); +const Callback = ({ setAuth, userManager }: Props) => { + const navigate = useNavigate(); useEffect(() => { - if (authenticated === null) { - userManager - .signinRedirectCallback() - .then((user: User) => { - if (user) { - setAuth(true); - setUserInfo(user); - } else { - setAuth(false); - } - }) - .catch((error: any) => { - setAuth(false); - }); - } - if (authenticated === true && userInfo === null) { - userManager - .getUser() - .then((user) => { - if (user) { - setAuth(true); - setUserInfo(user); - } else { - setAuth(false); - } - }) - .catch((error: any) => { + async function handleCallback() { + try { + // Exchange the code for tokens + const user = await userManager.signinRedirectCallback(); + + if (user) { + setAuth(true); + // Redirect to home or wherever you want + navigate("/", { replace: true }); + } else { setAuth(false); - }); + navigate("/", { replace: true }); + } + } catch (error) { + console.error("Callback error", error); + setAuth(false); + navigate("/", { replace: true }); + } } - }, [authenticated, userManager, setAuth]); - if (authenticated === true && userInfo) { - return ( -
-

Welcome, {userInfo.profile.name}!

-

Your ZITADEL Profile Information

-

Name: {userInfo.profile.name}

-

Email: {userInfo.profile.email}

-

Email Verified: {userInfo.profile.email_verified ? "Yes" : "No"}

-

- Roles:{" "} - {JSON.stringify( - userInfo.profile[ - "urn:zitadel:iam:org:project:roles" - ] - )} -

- -
- ); - } else { - return
Loading...
; - } + handleCallback(); + }, [userManager, setAuth, navigate]); + + return null; }; export default Callback; diff --git a/src/components/JWTContainer.tsx b/src/components/JWTContainer.tsx new file mode 100644 index 0000000..17cbccd --- /dev/null +++ b/src/components/JWTContainer.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; + +type Props = { + accessToken: string | null; + idToken: string | null; +}; + +function decodeJWT(jwt: string) { + // Split the JWT into its three parts: header, payload, and signature + const parts = jwt.split('.'); + + if (parts.length !== 3) { + throw new Error('Invalid JWT format'); + } + + // The payload is the second part (index 1) + const payloadBase64 = parts[1]; + + // Decode the base64-encoded payload + const decodedPayload = atob(payloadBase64.replace(/_/g, '/').replace(/-/g, '+')); + + // Parse the JSON string into an object + const payloadObj = JSON.parse(decodedPayload); + + return payloadObj; +} + +const JWTContainer = ({ accessToken, idToken }: Props) => { + const decodedAccessToken = accessToken ? JSON.stringify(decodeJWT(accessToken), null, 2) : null; + const decodedIdToken = idToken ? JSON.stringify(decodeJWT(idToken), null, 2) : null; + + const [copyState, setCopyState] = useState<{ [key: string]: boolean }>({}); + const handleCopy = (target: "accessToken" | "idToken", text: string) => { + navigator.clipboard.writeText(text).then(() => { + setCopyState((prev) => ({ ...prev, [target]: true })); + setTimeout(() => { + setCopyState((prev) => ({ ...prev, [target]: false })); + }, 1500); + }); + }; + + return ( +
+
+
+
+ Decoded Access Token + {accessToken && ( + handleCopy("accessToken", accessToken)} + > + )} +
+
+
{decodedAccessToken || "No token generated yet."}
+
+
+
+
+
+
+ Decoded ID Token + {idToken && ( + handleCopy("idToken", idToken)} + > + )} +
+
+
{decodedIdToken || "No token generated yet."}
+
+
+
+
+ ); +}; + +export default JWTContainer; \ No newline at end of file diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 124ff05..6bf1b79 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,26 +1,19 @@ -import { Navigate } from "react-router-dom"; - type Props = { handleLogin: () => void; + handleLogout: () => void; authenticated: boolean | null; }; -const Login = ({ authenticated, handleLogin }: Props) => { + +const Login = ({ authenticated, handleLogin, handleLogout }: Props) => { + const handleClick = authenticated ? handleLogout : handleLogin; + const label = authenticated ? "Logout" : "Login"; + const btnClass = authenticated ? "btn btn-outline-secondary logout-btn" : "btn btn-primary login-btn"; + return ( -
- {authenticated === null &&
Loading...
} - {authenticated === false && ( -
- -
- )} - {authenticated && } +
+
); }; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx new file mode 100644 index 0000000..f8f13b0 --- /dev/null +++ b/src/components/Navbar.tsx @@ -0,0 +1,29 @@ +const Navbar = () => { + return ( + + ); +}; + +export default Navbar; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 0e1cf3f..ec5e84f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1130,6 +1130,11 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.24.7", "@babel/runtime@^7.26.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.7": + version "7.28.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" + integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== + "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1688,11 +1693,52 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@react-aria/ssr@^3.5.0": + version "3.9.10" + resolved "https://registry.yarnpkg.com/@react-aria/ssr/-/ssr-3.9.10.tgz#7fdc09e811944ce0df1d7e713de1449abd7435e6" + integrity sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ== + dependencies: + "@swc/helpers" "^0.5.0" + "@remix-run/router@1.14.2": version "1.14.2" resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.14.2.tgz#4d58f59908d9197ba3179310077f25c88e49ed17" integrity sha512-ACXpdMM9hmKZww21yEqWwiLws/UPLhNKvimN8RrYSqPSvB3ov7sLvAcfvaxePeLvccTQKGdkDIhLYApZVDFuKg== +"@restart/hooks@^0.4.9": + version "0.4.16" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.4.16.tgz#95ae8ac1cc7e2bd4fed5e39800ff85604c6d59fb" + integrity sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w== + dependencies: + dequal "^2.0.3" + +"@restart/hooks@^0.5.0": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@restart/hooks/-/hooks-0.5.1.tgz#6776b3859e33aea72b23b81fc47021edf17fd247" + integrity sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q== + dependencies: + dequal "^2.0.3" + +"@restart/ui@^1.9.4": + version "1.9.4" + resolved "https://registry.yarnpkg.com/@restart/ui/-/ui-1.9.4.tgz#9d61f56f2647f5ab8a33d87b278b9ce183511a26" + integrity sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA== + dependencies: + "@babel/runtime" "^7.26.0" + "@popperjs/core" "^2.11.8" + "@react-aria/ssr" "^3.5.0" + "@restart/hooks" "^0.5.0" + "@types/warning" "^3.0.3" + dequal "^2.0.3" + dom-helpers "^5.2.0" + uncontrollable "^8.0.4" + warning "^4.0.3" + "@rollup/plugin-babel@^5.2.0": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" @@ -1872,6 +1918,13 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@swc/helpers@^0.5.0": + version "0.5.17" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.17.tgz#5a7be95ac0f0bf186e7e6e890e7a6f6cda6ce971" + integrity sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A== + dependencies: + tslib "^2.8.0" + "@testing-library/dom@^8.5.0": version "8.20.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.20.1.tgz#2e52a32e46fc88369eef7eef634ac2a192decd9f" @@ -2146,6 +2199,11 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== +"@types/prop-types@^15.7.12": + version "15.7.15" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" + integrity sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== + "@types/q@^1.5.1": version "1.5.8" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.8.tgz#95f6c6a08f2ad868ba230ead1d2d7f7be3db3837" @@ -2168,6 +2226,11 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.6": + version "4.4.12" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.12.tgz#b5d76568485b02a307238270bfe96cb51ee2a044" + integrity sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== + "@types/react@*", "@types/react@^18.0.0": version "18.2.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" @@ -2177,6 +2240,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@>=16.9.11": + version "19.1.13" + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.13.tgz#fc650ffa680d739a25a530f5d7ebe00cdd771883" + integrity sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ== + dependencies: + csstype "^3.0.2" + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -2247,6 +2317,11 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== +"@types/warning@^3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.3.tgz#d1884c8cc4a426d1ac117ca2611bf333834c6798" + integrity sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q== + "@types/ws@^8.5.5": version "8.5.10" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" @@ -2501,9 +2576,8 @@ integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== "@zitadel/react@link:./lib": - version "0.0.0-development" - dependencies: - oidc-client-ts "^2.4.0" + version "0.0.0" + uid "" abab@^2.0.3, abab@^2.0.5: version "2.0.6" @@ -3059,6 +3133,11 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +bootstrap@^5.3.8: + version "5.3.8" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed" + integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -3276,6 +3355,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ== +classnames@^2.3.2: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clean-css@^5.2.2: version "5.3.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.3.tgz#b330653cd3bd6b75009cc25c714cae7b93351ccd" @@ -3933,6 +4017,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -5370,6 +5462,13 @@ internal-slot@^1.0.4, internal-slot@^1.0.5: hasown "^2.0.0" side-channel "^1.0.4" +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -6544,7 +6643,7 @@ lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7835,7 +7934,15 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.8.1: +prop-types-extra@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/prop-types-extra/-/prop-types-extra-1.1.1.tgz#58c3b74cbfbb95d304625975aa2f0848329a010b" + integrity sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew== + dependencies: + react-is "^16.3.2" + warning "^4.0.0" + +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7925,6 +8032,25 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-bootstrap@^2.10.10: + version "2.10.10" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-2.10.10.tgz#be0b0d951a69987152d75c0e6986c80425efdf21" + integrity sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ== + dependencies: + "@babel/runtime" "^7.24.7" + "@restart/hooks" "^0.4.9" + "@restart/ui" "^1.9.4" + "@types/prop-types" "^15.7.12" + "@types/react-transition-group" "^4.4.6" + classnames "^2.3.2" + dom-helpers "^5.2.1" + invariant "^2.2.4" + prop-types "^15.8.1" + prop-types-extra "^1.1.0" + react-transition-group "^4.4.5" + uncontrollable "^7.2.1" + warning "^4.0.3" + react-dev-utils@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73" @@ -7968,7 +8094,7 @@ react-error-overlay@^6.0.11: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.11.tgz#92835de5841c5cf08ba00ddd2d677b6d17ff9adb" integrity sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg== -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.3.2: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -7983,6 +8109,11 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-refresh@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" @@ -8058,6 +8189,16 @@ react-scripts@5.0.1: optionalDependencies: fsevents "^2.3.2" +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" @@ -9191,6 +9332,11 @@ tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + tsutils@^3.21.0: version "3.21.0" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" @@ -9301,6 +9447,21 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncontrollable@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" + integrity sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ== + dependencies: + "@babel/runtime" "^7.6.3" + "@types/react" ">=16.9.11" + invariant "^2.2.4" + react-lifecycles-compat "^3.0.4" + +uncontrollable@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-8.0.4.tgz#a0a8307f638795162fafd0550f4a1efa0f8c5eb6" + integrity sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ== + underscore@1.12.1: version "1.12.1" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.12.1.tgz#7bb8cc9b3d397e201cf8553336d262544ead829e" @@ -9462,6 +9623,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warning@^4.0.0, warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da"