@oondemand/oon-core-front 0.4.11 → 0.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1138,8 +1138,10 @@ interface TenantStorage {
1138
1138
  clear(): void;
1139
1139
  }
1140
1140
  /**
1141
- * Guarda somente a seleção de tenant do usuário. O valor persistido não é
1142
- * autoridade: o backend/Control Plane precisa validar o header a cada request.
1141
+ * Mantém o tenant no escopo da aba. Isso permite abrir Tenant A e Tenant B em
1142
+ * janelas diferentes sem uma aba trocar o contexto da outra. O query param
1143
+ * `tenant` transporta somente o identificador selecionado; o backend continua
1144
+ * validando o membership em todas as requisições.
1143
1145
  */
1144
1146
  declare function createTenantStorage(appId: string): TenantStorage;
1145
1147
 
package/dist/index.js CHANGED
@@ -160,24 +160,37 @@ function normalize(value3) {
160
160
  const normalized3 = String(value3 ?? "").trim();
161
161
  return normalized3 || null;
162
162
  }
163
+ function tenantFromUrl(key) {
164
+ if (typeof window === "undefined") return null;
165
+ const url = new URL(window.location.href);
166
+ const tenantId = normalize(url.searchParams.get("tenant"));
167
+ if (!tenantId) return null;
168
+ window.sessionStorage.setItem(key, tenantId);
169
+ url.searchParams.delete("tenant");
170
+ window.history.replaceState(window.history.state, "", `${url.pathname}${url.search}${url.hash}`);
171
+ return tenantId;
172
+ }
163
173
  function createTenantStorage(appId) {
164
174
  const key = `oon:${appId}:tenant`;
165
175
  return {
166
176
  get() {
167
177
  if (typeof window === "undefined") return null;
168
- return normalize(window.localStorage.getItem(key));
178
+ const incoming = tenantFromUrl(key);
179
+ if (incoming) return incoming;
180
+ window.localStorage.removeItem(key);
181
+ return normalize(window.sessionStorage.getItem(key));
169
182
  },
170
183
  set(tenantId) {
171
184
  if (typeof window === "undefined") return;
172
185
  const value3 = normalize(tenantId);
173
186
  if (!value3) {
174
- window.localStorage.removeItem(key);
187
+ window.sessionStorage.removeItem(key);
175
188
  return;
176
189
  }
177
- window.localStorage.setItem(key, value3);
190
+ window.sessionStorage.setItem(key, value3);
178
191
  },
179
192
  clear() {
180
- if (typeof window !== "undefined") window.localStorage.removeItem(key);
193
+ if (typeof window !== "undefined") window.sessionStorage.removeItem(key);
181
194
  }
182
195
  };
183
196
  }
@@ -5278,7 +5291,7 @@ function Sidebar({ app, menu, registry }) {
5278
5291
  // package.json
5279
5292
  var package_default = {
5280
5293
  name: "@oondemand/oon-core-front",
5281
- version: "0.4.11",
5294
+ version: "0.4.13",
5282
5295
  repository: {
5283
5296
  type: "git",
5284
5297
  url: "git+https://github.com/oondemand/oon-platform.git",
@@ -5734,16 +5747,7 @@ function AppAccessGuard({ children }) {
5734
5747
 
5735
5748
  // src/security/LoginPage.tsx
5736
5749
  import { useState as useState18 } from "react";
5737
- import {
5738
- Box as Box25,
5739
- Button as Button18,
5740
- Flex as Flex18,
5741
- Heading as Heading14,
5742
- Input as Input10,
5743
- SimpleGrid as SimpleGrid6,
5744
- Text as Text26,
5745
- VStack as VStack5
5746
- } from "@chakra-ui/react";
5750
+ import { Box as Box25, Button as Button18, Flex as Flex18, Heading as Heading14, Input as Input10, SimpleGrid as SimpleGrid6, Text as Text26, VStack as VStack5 } from "@chakra-ui/react";
5747
5751
  import { Link as Link3, Navigate as Navigate2, useLocation as useLocation4 } from "react-router-dom";
5748
5752
  import { jsx as jsx37, jsxs as jsxs26 } from "react/jsx-runtime";
5749
5753
  function tenantOptions(error) {
@@ -5754,17 +5758,22 @@ function tenantOptions(error) {
5754
5758
  return tenants.filter((tenant) => Boolean(tenant) && typeof tenant === "object").map((tenant) => ({
5755
5759
  id: String(tenant.id || ""),
5756
5760
  nome: String(tenant.nome || tenant.slug || tenant.id || ""),
5757
- slug: tenant.slug ? String(tenant.slug) : void 0
5761
+ slug: tenant.slug ? String(tenant.slug) : void 0,
5762
+ tipo: tenant.tipo ? String(tenant.tipo) : void 0,
5763
+ status: tenant.status ? String(tenant.status) : void 0
5758
5764
  })).filter((tenant) => tenant.id && tenant.nome);
5759
5765
  }
5760
5766
  function safeRedirect(search) {
5761
5767
  const value3 = new URLSearchParams(search).get("redirect") || "/";
5762
5768
  if (!value3.startsWith("/") || value3.startsWith("//")) return "/";
5763
- if (value3.startsWith("/login") || value3.startsWith("/acesso-negado")) {
5764
- return "/";
5765
- }
5769
+ if (value3.startsWith("/login") || value3.startsWith("/acesso-negado")) return "/";
5766
5770
  return value3;
5767
5771
  }
5772
+ function tenantUrl(path, tenantId) {
5773
+ const target = new URL(path, window.location.origin);
5774
+ target.searchParams.set("tenant", tenantId);
5775
+ return target.toString();
5776
+ }
5768
5777
  function LoginPage({ app, onboardingEnabled = false }) {
5769
5778
  const { status, login } = useOonAuth();
5770
5779
  const location = useLocation4();
@@ -5773,168 +5782,92 @@ function LoginPage({ app, onboardingEnabled = false }) {
5773
5782
  const [submitting, setSubmitting] = useState18(false);
5774
5783
  const [message, setMessage] = useState18(null);
5775
5784
  const [tenants, setTenants] = useState18([]);
5776
- const [tenantId, setTenantId] = useState18("");
5785
+ const [selectorMode, setSelectorMode] = useState18(false);
5786
+ const [openingTenant, setOpeningTenant] = useState18(null);
5777
5787
  const redirect = safeRedirect(location.search);
5778
- if (status === "authenticated") {
5779
- return /* @__PURE__ */ jsx37(Navigate2, { to: redirect, replace: true });
5780
- }
5781
- if (status === "forbidden") {
5782
- return /* @__PURE__ */ jsx37(Navigate2, { to: "/acesso-negado", replace: true });
5783
- }
5788
+ if (status === "authenticated" && !selectorMode) return /* @__PURE__ */ jsx37(Navigate2, { to: redirect, replace: true });
5789
+ if (status === "forbidden") return /* @__PURE__ */ jsx37(Navigate2, { to: "/acesso-negado", replace: true });
5784
5790
  async function handleSubmit(event) {
5785
5791
  event.preventDefault();
5792
+ if (tenants.length > 1) return;
5786
5793
  setSubmitting(true);
5787
5794
  setMessage(null);
5788
5795
  try {
5789
- if (tenants.length > 1 && !tenantId) {
5790
- setMessage("Selecione a organiza\xE7\xE3o para continuar.");
5791
- return;
5792
- }
5793
- await login(email.trim(), password, tenantId || void 0);
5796
+ await login(email.trim(), password);
5794
5797
  } catch (error) {
5795
5798
  const authError = error;
5796
5799
  if (authError.code === "TENANT_SELECTION_REQUIRED") {
5797
5800
  const options = tenantOptions(authError);
5798
5801
  if (options.length > 1) {
5799
5802
  setTenants(options);
5800
- setTenantId("");
5801
- setMessage("Seu usu\xE1rio possui acesso a mais de uma organiza\xE7\xE3o. Selecione uma para continuar.");
5803
+ setMessage("Escolha uma organiza\xE7\xE3o. O tenant ser\xE1 aberto em uma nova aba.");
5802
5804
  return;
5803
5805
  }
5804
5806
  }
5805
5807
  if (authError.status === 403) return;
5806
- setMessage(
5807
- authError.status === 401 ? "E-mail ou senha inv\xE1lidos." : authError.message || "N\xE3o foi poss\xEDvel entrar. Tente novamente."
5808
- );
5808
+ setMessage(authError.status === 401 ? "E-mail ou senha inv\xE1lidos." : authError.message || "N\xE3o foi poss\xEDvel entrar. Tente novamente.");
5809
5809
  } finally {
5810
5810
  setSubmitting(false);
5811
5811
  }
5812
5812
  }
5813
- return /* @__PURE__ */ jsx37(Flex18, { minH: "100vh", align: "center", justify: "center", bg: "#F5F8FA", px: 4, children: /* @__PURE__ */ jsx37(
5814
- Box25,
5815
- {
5816
- width: "100%",
5817
- maxW: tenants.length > 1 ? "680px" : "440px",
5818
- bg: "white",
5819
- borderWidth: "1px",
5820
- borderColor: "#E4EBEF",
5821
- borderRadius: "18px",
5822
- boxShadow: "0 24px 70px rgba(7, 38, 46, 0.10)",
5823
- p: { base: 6, md: 9 },
5824
- children: /* @__PURE__ */ jsx37("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs26(VStack5, { align: "stretch", gap: 6, children: [
5825
- /* @__PURE__ */ jsxs26(Box25, { children: [
5826
- /* @__PURE__ */ jsx37(
5827
- Text26,
5828
- {
5829
- color: "#39BFED",
5830
- fontSize: "xs",
5831
- fontWeight: "700",
5832
- letterSpacing: "0.12em",
5833
- textTransform: "uppercase",
5834
- children: "Central Oon"
5835
- }
5836
- ),
5837
- /* @__PURE__ */ jsx37(Heading14, { mt: 2, size: "lg", color: "#07262E", children: app.title ?? app.name }),
5838
- /* @__PURE__ */ jsx37(Text26, { mt: 2, color: "gray.600", fontSize: "sm", children: "Entre com seu usu\xE1rio autorizado para acessar este aplicativo." })
5839
- ] }),
5840
- /* @__PURE__ */ jsxs26(VStack5, { align: "stretch", gap: 4, children: [
5841
- /* @__PURE__ */ jsxs26(Box25, { children: [
5842
- /* @__PURE__ */ jsx37(Text26, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "E-mail" }),
5843
- /* @__PURE__ */ jsx37(
5844
- Input10,
5845
- {
5846
- type: "email",
5847
- value: email,
5848
- onChange: (event) => {
5849
- setEmail(event.target.value);
5850
- setTenants([]);
5851
- setTenantId("");
5852
- },
5853
- autoComplete: "email",
5854
- placeholder: "voce@empresa.com.br",
5855
- required: true
5856
- }
5857
- )
5858
- ] }),
5859
- /* @__PURE__ */ jsxs26(Box25, { children: [
5860
- /* @__PURE__ */ jsx37(Text26, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Senha" }),
5861
- /* @__PURE__ */ jsx37(
5862
- Input10,
5863
- {
5864
- type: "password",
5865
- value: password,
5866
- onChange: (event) => setPassword(event.target.value),
5867
- autoComplete: "current-password",
5868
- placeholder: "Sua senha",
5869
- required: true
5870
- }
5871
- )
5872
- ] }),
5873
- tenants.length > 1 ? /* @__PURE__ */ jsxs26(Box25, { children: [
5874
- /* @__PURE__ */ jsx37(Text26, { mb: 3, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Escolha o tenant para acessar" }),
5875
- /* @__PURE__ */ jsx37(SimpleGrid6, { columns: { base: 1, md: 2 }, gap: 3, children: tenants.map((tenant) => {
5876
- const selected = tenantId === tenant.id;
5877
- return /* @__PURE__ */ jsx37(
5878
- Button18,
5879
- {
5880
- type: "button",
5881
- variant: "outline",
5882
- height: "auto",
5883
- minH: "88px",
5884
- justifyContent: "flex-start",
5885
- whiteSpace: "normal",
5886
- textAlign: "left",
5887
- borderWidth: "2px",
5888
- borderColor: selected ? "#39BFED" : "#D9E2E7",
5889
- bg: selected ? "#EFFAFF" : "white",
5890
- color: "#24323A",
5891
- "aria-pressed": selected,
5892
- onClick: () => {
5893
- setTenantId(tenant.id);
5894
- setMessage(null);
5895
- },
5896
- _hover: { borderColor: "#39BFED", bg: "#F6FCFE" },
5897
- children: /* @__PURE__ */ jsxs26(VStack5, { align: "start", gap: 1, py: 3, children: [
5898
- /* @__PURE__ */ jsx37(Text26, { fontWeight: "700", children: tenant.nome }),
5899
- tenant.slug ? /* @__PURE__ */ jsx37(Text26, { fontSize: "xs", color: "gray.500", children: tenant.slug }) : null
5900
- ] })
5901
- },
5902
- tenant.id
5903
- );
5904
- }) })
5905
- ] }) : null
5906
- ] }),
5907
- message ? /* @__PURE__ */ jsx37(
5908
- Box25,
5909
- {
5910
- role: "alert",
5911
- borderRadius: "10px",
5912
- bg: "red.50",
5913
- color: "red.700",
5914
- px: 4,
5915
- py: 3,
5916
- fontSize: "sm",
5917
- children: message
5918
- }
5919
- ) : null,
5920
- /* @__PURE__ */ jsx37(
5921
- Button18,
5922
- {
5923
- type: "submit",
5924
- bg: "#07262E",
5925
- color: "white",
5926
- _hover: { bg: "#0B3843" },
5927
- disabled: submitting || !email.trim() || !password || tenants.length > 1 && !tenantId,
5928
- children: submitting ? "Validando acesso..." : "Entrar"
5929
- }
5930
- ),
5931
- app.kind === "portal-cockpit" && onboardingEnabled ? /* @__PURE__ */ jsxs26(Text26, { textAlign: "center", fontSize: "sm", color: "gray.600", children: [
5932
- "Ainda n\xE3o possui acesso? ",
5933
- /* @__PURE__ */ jsx37(Link3, { to: "/cadastro", children: "Criar cadastro" })
5934
- ] }) : null
5935
- ] }) })
5813
+ async function openTenant(tenant) {
5814
+ if (openingTenant) return;
5815
+ const popup = window.open("about:blank", "_blank");
5816
+ if (!popup) {
5817
+ setMessage("Permita pop-ups para abrir o tenant em uma nova aba.");
5818
+ return;
5936
5819
  }
5937
- ) });
5820
+ popup.opener = null;
5821
+ setOpeningTenant(tenant.id);
5822
+ setSelectorMode(true);
5823
+ setMessage(null);
5824
+ try {
5825
+ if (status !== "authenticated") await login(email.trim(), password, tenant.id);
5826
+ popup.location.replace(tenantUrl(redirect, tenant.id));
5827
+ setPassword("");
5828
+ setMessage(`${tenant.nome} foi aberto em uma nova aba. Escolha outro tenant quando precisar.`);
5829
+ } catch (error) {
5830
+ popup.close();
5831
+ const authError = error;
5832
+ setMessage(authError.message || "N\xE3o foi poss\xEDvel abrir o tenant.");
5833
+ } finally {
5834
+ setOpeningTenant(null);
5835
+ }
5836
+ }
5837
+ return /* @__PURE__ */ jsx37(Flex18, { minH: "100vh", align: "center", justify: "center", bg: "#F5F8FA", px: 4, children: /* @__PURE__ */ jsx37(Box25, { width: "100%", maxW: tenants.length > 1 ? "760px" : "440px", bg: "white", borderWidth: "1px", borderColor: "#E4EBEF", borderRadius: "18px", boxShadow: "0 24px 70px rgba(7, 38, 46, 0.10)", p: { base: 6, md: 9 }, children: /* @__PURE__ */ jsx37("form", { onSubmit: handleSubmit, children: /* @__PURE__ */ jsxs26(VStack5, { align: "stretch", gap: 6, children: [
5838
+ /* @__PURE__ */ jsxs26(Box25, { children: [
5839
+ /* @__PURE__ */ jsx37(Text26, { color: "#39BFED", fontSize: "xs", fontWeight: "700", letterSpacing: "0.12em", textTransform: "uppercase", children: "Central Oon" }),
5840
+ /* @__PURE__ */ jsx37(Heading14, { mt: 2, size: "lg", color: "#07262E", children: app.title ?? app.name }),
5841
+ /* @__PURE__ */ jsx37(Text26, { mt: 2, color: "gray.600", fontSize: "sm", children: tenants.length > 1 ? "Abra uma ou mais organiza\xE7\xF5es para trabalhar em contextos separados." : "Entre com seu usu\xE1rio autorizado para acessar este aplicativo." })
5842
+ ] }),
5843
+ tenants.length <= 1 ? /* @__PURE__ */ jsxs26(VStack5, { align: "stretch", gap: 4, children: [
5844
+ /* @__PURE__ */ jsxs26(Box25, { children: [
5845
+ /* @__PURE__ */ jsx37(Text26, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "E-mail" }),
5846
+ /* @__PURE__ */ jsx37(Input10, { type: "email", value: email, onChange: (event) => {
5847
+ setEmail(event.target.value);
5848
+ setTenants([]);
5849
+ setSelectorMode(false);
5850
+ }, autoComplete: "email", placeholder: "voce@empresa.com.br", required: true })
5851
+ ] }),
5852
+ /* @__PURE__ */ jsxs26(Box25, { children: [
5853
+ /* @__PURE__ */ jsx37(Text26, { mb: 2, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Senha" }),
5854
+ /* @__PURE__ */ jsx37(Input10, { type: "password", value: password, onChange: (event) => setPassword(event.target.value), autoComplete: "current-password", placeholder: "Sua senha", required: true })
5855
+ ] })
5856
+ ] }) : /* @__PURE__ */ jsxs26(Box25, { children: [
5857
+ /* @__PURE__ */ jsx37(Text26, { mb: 3, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Escolha o tenant para acessar" }),
5858
+ /* @__PURE__ */ jsx37(SimpleGrid6, { columns: { base: 1, md: 2 }, gap: 3, children: tenants.map((tenant) => /* @__PURE__ */ jsx37(Button18, { type: "button", variant: "outline", height: "auto", minH: "104px", justifyContent: "flex-start", whiteSpace: "normal", textAlign: "left", borderWidth: "2px", borderColor: "#D9E2E7", bg: "white", color: "#24323A", disabled: Boolean(openingTenant), "aria-label": `Abrir ${tenant.nome} em nova aba`, onClick: () => void openTenant(tenant), _hover: { borderColor: "#39BFED", bg: "#F6FCFE" }, children: /* @__PURE__ */ jsxs26(VStack5, { align: "start", gap: 1, py: 3, children: [
5859
+ /* @__PURE__ */ jsx37(Text26, { fontWeight: "700", children: tenant.nome }),
5860
+ /* @__PURE__ */ jsx37(Text26, { fontSize: "xs", color: "gray.500", children: [tenant.slug, tenant.tipo, tenant.status].filter(Boolean).join(" \xB7 ") }),
5861
+ /* @__PURE__ */ jsx37(Text26, { fontSize: "xs", color: "#168EB9", children: openingTenant === tenant.id ? "Abrindo..." : "Abrir em nova aba" })
5862
+ ] }) }, tenant.id)) })
5863
+ ] }),
5864
+ message ? /* @__PURE__ */ jsx37(Box25, { role: "status", borderRadius: "10px", bg: "blue.50", color: "blue.800", px: 4, py: 3, fontSize: "sm", children: message }) : null,
5865
+ tenants.length <= 1 ? /* @__PURE__ */ jsx37(Button18, { type: "submit", bg: "#07262E", color: "white", _hover: { bg: "#0B3843" }, disabled: submitting || !email.trim() || !password, children: submitting ? "Validando acesso..." : "Entrar" }) : null,
5866
+ app.kind === "portal-cockpit" && onboardingEnabled && tenants.length <= 1 ? /* @__PURE__ */ jsxs26(Text26, { textAlign: "center", fontSize: "sm", color: "gray.600", children: [
5867
+ "Ainda n\xE3o possui acesso? ",
5868
+ /* @__PURE__ */ jsx37(Link3, { to: "/cadastro", children: "Criar conta" })
5869
+ ] }) : null
5870
+ ] }) }) }) });
5938
5871
  }
5939
5872
 
5940
5873
  // src/security/FirstAccessPage.tsx