@oondemand/oon-core-front 0.4.2 → 0.4.4

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
@@ -1095,7 +1095,7 @@ interface AuthContextValue {
1095
1095
  status: AuthStatus;
1096
1096
  isAuthenticated: boolean;
1097
1097
  hasPermission: (permission?: string | string[]) => boolean;
1098
- login: (email: string, password: string) => Promise<void>;
1098
+ login: (email: string, password: string, tenantId?: string) => Promise<void>;
1099
1099
  completeFirstAccess: (password: string) => Promise<void>;
1100
1100
  retry: () => Promise<void>;
1101
1101
  logout: () => void;
package/dist/index.js CHANGED
@@ -539,11 +539,11 @@ var oonSystem = createOonSystem();
539
539
 
540
540
  // src/security/AuthProvider.tsx
541
541
  import {
542
- createContext as createContext3,
543
- useCallback as useCallback2,
544
- useContext as useContext3,
542
+ createContext as createContext4,
543
+ useCallback as useCallback3,
544
+ useContext as useContext4,
545
545
  useEffect as useEffect4,
546
- useState as useState4
546
+ useState as useState5
547
547
  } from "react";
548
548
 
549
549
  // src/security/ActivationProvider.tsx
@@ -598,9 +598,65 @@ function useActivation() {
598
598
  return context;
599
599
  }
600
600
 
601
- // src/security/AuthProvider.tsx
601
+ // src/security/TenantProvider.tsx
602
+ import {
603
+ createContext as createContext3,
604
+ useCallback as useCallback2,
605
+ useContext as useContext3,
606
+ useMemo as useMemo2,
607
+ useState as useState4
608
+ } from "react";
609
+ import { useQueryClient } from "@tanstack/react-query";
602
610
  import { jsx as jsx5 } from "react/jsx-runtime";
603
- var AuthContext = createContext3(null);
611
+ var TenantContext = createContext3(null);
612
+ function applyHeader(http, tenantId) {
613
+ if (tenantId) {
614
+ http.defaults.headers.common["x-oon-tenant-id"] = tenantId;
615
+ return;
616
+ }
617
+ delete http.defaults.headers.common["x-oon-tenant-id"];
618
+ }
619
+ function TenantProvider({ http, storage, children }) {
620
+ const queryClient = useQueryClient();
621
+ const [activeTenantId, setActiveTenantId] = useState4(() => storage.get());
622
+ const replaceTenant = useCallback2(
623
+ (tenantId) => {
624
+ const normalized2 = String(tenantId ?? "").trim() || null;
625
+ if (normalized2) storage.set(normalized2);
626
+ else storage.clear();
627
+ applyHeader(http, normalized2);
628
+ setActiveTenantId(normalized2);
629
+ queryClient.clear();
630
+ },
631
+ [http, queryClient, storage]
632
+ );
633
+ const value2 = useMemo2(
634
+ () => ({
635
+ activeTenantId,
636
+ switchTenant(tenantId) {
637
+ if (!tenantId?.trim()) throw new Error("tenantId \xE9 obrigat\xF3rio.");
638
+ replaceTenant(tenantId);
639
+ },
640
+ clearTenant() {
641
+ replaceTenant(null);
642
+ }
643
+ }),
644
+ [activeTenantId, replaceTenant]
645
+ );
646
+ return /* @__PURE__ */ jsx5(TenantContext.Provider, { value: value2, children });
647
+ }
648
+ function useOptionalOonTenant() {
649
+ return useContext3(TenantContext);
650
+ }
651
+ function useOonTenant() {
652
+ const value2 = useOptionalOonTenant();
653
+ if (!value2) throw new Error("useOonTenant deve ser usado dentro de TenantProvider.");
654
+ return value2;
655
+ }
656
+
657
+ // src/security/AuthProvider.tsx
658
+ import { jsx as jsx6 } from "react/jsx-runtime";
659
+ var AuthContext = createContext4(null);
604
660
  function errorStatus(error) {
605
661
  return error?.status;
606
662
  }
@@ -648,20 +704,26 @@ function can(user, permission) {
648
704
  function AuthProvider({ http, storage, auth, loginUrl, children }) {
649
705
  const tokenParam = auth?.tokenParam ?? "code";
650
706
  const devToken = auth?.devToken?.trim() || null;
651
- const [user, setUser] = useState4(null);
652
- const [status, setStatus] = useState4("loading");
707
+ const [user, setUser] = useState5(null);
708
+ const [status, setStatus] = useState5("loading");
653
709
  const activation = useOptionalActivation();
654
- const redirectToExternalLogin = useCallback2(() => {
710
+ const tenantContext = useOptionalOonTenant();
711
+ const redirectToExternalLogin = useCallback3(() => {
655
712
  if (loginUrl) window.location.href = loginUrl;
656
713
  }, [loginUrl]);
657
- const applyUser = useCallback2((next) => {
714
+ const applyUser = useCallback3((next) => {
658
715
  setUser(next);
716
+ const tenant = next.tenant;
717
+ const resolvedTenantId = String(next.tenantId || tenant?.id || "").trim();
718
+ if (resolvedTenantId && tenantContext?.activeTenantId !== resolvedTenantId) {
719
+ tenantContext?.switchTenant(resolvedTenantId);
720
+ }
659
721
  const firstAccess = Boolean(
660
722
  next.mustChangePassword || next.firstAccessPending
661
723
  );
662
724
  setStatus(firstAccess ? "first-access" : "authenticated");
663
- }, []);
664
- const applyValidationError = useCallback2(
725
+ }, [tenantContext]);
726
+ const applyValidationError = useCallback3(
665
727
  (error) => {
666
728
  const currentStatus = errorStatus(error);
667
729
  setUser(null);
@@ -679,11 +741,11 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
679
741
  },
680
742
  [storage, redirectToExternalLogin]
681
743
  );
682
- const validateSession = useCallback2(async () => {
744
+ const validateSession = useCallback3(async () => {
683
745
  const response = await http.get("/auth/validar-token");
684
746
  applyUser(response.data.usuario);
685
747
  }, [http, applyUser]);
686
- const retry = useCallback2(async () => {
748
+ const retry = useCallback3(async () => {
687
749
  setStatus("loading");
688
750
  try {
689
751
  await validateSession();
@@ -692,15 +754,23 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
692
754
  throw error;
693
755
  }
694
756
  }, [validateSession, applyValidationError]);
695
- const login = useCallback2(
696
- async (email, password) => {
757
+ const login = useCallback3(
758
+ async (email, password, tenantId) => {
697
759
  setStatus("loading");
698
760
  setUser(null);
699
761
  try {
762
+ const normalizedTenantId = String(tenantId || "").trim();
763
+ if (normalizedTenantId) tenantContext?.switchTenant(normalizedTenantId);
764
+ else tenantContext?.clearTenant();
700
765
  const response = await http.post(
701
766
  "/auth/autenticar",
702
767
  {},
703
- { headers: { Authorization: `Basic ${encodeBasicCredentials(email, password)}` } }
768
+ {
769
+ headers: {
770
+ Authorization: `Basic ${encodeBasicCredentials(email, password)}`,
771
+ ...normalizedTenantId ? { "x-oon-tenant-id": normalizedTenantId } : {}
772
+ }
773
+ }
704
774
  );
705
775
  if (!response.data.token) {
706
776
  const error = { code: "AUTH_TOKEN_MISSING", message: "Token n\xE3o retornado pelo backend.", status: 502 };
@@ -709,22 +779,27 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
709
779
  storage.set(response.data.token);
710
780
  await validateSession();
711
781
  } catch (error) {
712
- applyValidationError(error);
782
+ if (error?.code === "TENANT_SELECTION_REQUIRED") {
783
+ setStatus("unauthenticated");
784
+ } else {
785
+ applyValidationError(error);
786
+ }
713
787
  throw error;
714
788
  }
715
789
  },
716
- [http, storage, validateSession, applyValidationError]
790
+ [http, storage, validateSession, applyValidationError, tenantContext]
717
791
  );
718
- const completeFirstAccess = useCallback2(async (password) => {
792
+ const completeFirstAccess = useCallback3(async (password) => {
719
793
  await http.post("/auth/first-access/password", { password });
720
794
  await validateSession();
721
795
  }, [http, validateSession]);
722
- const logout = useCallback2(() => {
796
+ const logout = useCallback3(() => {
723
797
  storage.clear();
798
+ tenantContext?.clearTenant();
724
799
  setUser(null);
725
800
  setStatus("unauthenticated");
726
801
  redirectToExternalLogin();
727
- }, [storage, redirectToExternalLogin]);
802
+ }, [storage, redirectToExternalLogin, tenantContext]);
728
803
  useEffect4(() => {
729
804
  let active = true;
730
805
  if (activation && !["active", "error"].includes(activation.status)) {
@@ -758,7 +833,7 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
758
833
  const roles = roleCode ? [roleCode] : [];
759
834
  const capabilities = extractCapabilities(user);
760
835
  const permissions = explicitPermissions(user) ?? capabilities;
761
- const hasPermission = useCallback2(
836
+ const hasPermission = useCallback3(
762
837
  (permission) => can(user, permission),
763
838
  [user]
764
839
  );
@@ -775,67 +850,14 @@ function AuthProvider({ http, storage, auth, loginUrl, children }) {
775
850
  retry,
776
851
  logout
777
852
  };
778
- return /* @__PURE__ */ jsx5(AuthContext.Provider, { value: value2, children });
853
+ return /* @__PURE__ */ jsx6(AuthContext.Provider, { value: value2, children });
779
854
  }
780
855
  function useOonAuth() {
781
- const context = useContext3(AuthContext);
856
+ const context = useContext4(AuthContext);
782
857
  if (!context) throw new Error("useOonAuth deve ser usado dentro de <AuthProvider> (oonCoreFront.start).");
783
858
  return context;
784
859
  }
785
860
 
786
- // src/security/TenantProvider.tsx
787
- import {
788
- createContext as createContext4,
789
- useCallback as useCallback3,
790
- useContext as useContext4,
791
- useMemo as useMemo2,
792
- useState as useState5
793
- } from "react";
794
- import { useQueryClient } from "@tanstack/react-query";
795
- import { jsx as jsx6 } from "react/jsx-runtime";
796
- var TenantContext = createContext4(null);
797
- function applyHeader(http, tenantId) {
798
- if (tenantId) {
799
- http.defaults.headers.common["x-oon-tenant-id"] = tenantId;
800
- return;
801
- }
802
- delete http.defaults.headers.common["x-oon-tenant-id"];
803
- }
804
- function TenantProvider({ http, storage, children }) {
805
- const queryClient = useQueryClient();
806
- const [activeTenantId, setActiveTenantId] = useState5(() => storage.get());
807
- const replaceTenant = useCallback3(
808
- (tenantId) => {
809
- const normalized2 = String(tenantId ?? "").trim() || null;
810
- if (normalized2) storage.set(normalized2);
811
- else storage.clear();
812
- applyHeader(http, normalized2);
813
- setActiveTenantId(normalized2);
814
- queryClient.clear();
815
- },
816
- [http, queryClient, storage]
817
- );
818
- const value2 = useMemo2(
819
- () => ({
820
- activeTenantId,
821
- switchTenant(tenantId) {
822
- if (!tenantId?.trim()) throw new Error("tenantId \xE9 obrigat\xF3rio.");
823
- replaceTenant(tenantId);
824
- },
825
- clearTenant() {
826
- replaceTenant(null);
827
- }
828
- }),
829
- [activeTenantId, replaceTenant]
830
- );
831
- return /* @__PURE__ */ jsx6(TenantContext.Provider, { value: value2, children });
832
- }
833
- function useOonTenant() {
834
- const value2 = useContext4(TenantContext);
835
- if (!value2) throw new Error("useOonTenant deve ser usado dentro de TenantProvider.");
836
- return value2;
837
- }
838
-
839
861
  // src/app/OonProviders.tsx
840
862
  import { jsx as jsx7 } from "react/jsx-runtime";
841
863
  function OonProviders({
@@ -2799,6 +2821,7 @@ function CoreRelatedGrid({
2799
2821
  onRefresh
2800
2822
  }) {
2801
2823
  const tab = rawTab;
2824
+ const { http } = useOonApi();
2802
2825
  const relation = relationFrom(tab.relation, relations);
2803
2826
  const schemaQuery = useModelSchema(relation?.model ?? "");
2804
2827
  const schema = schemaQuery.data;
@@ -2852,11 +2875,21 @@ function CoreRelatedGrid({
2852
2875
  onError: setEditError
2853
2876
  });
2854
2877
  const createMutation = useMutation({
2855
- mutationFn: (values) => resource.create({
2856
- ...createInitialValues,
2857
- ...values,
2858
- ...relation && parentValue != null ? { [relation.foreignKey]: parentValue } : {}
2859
- }),
2878
+ mutationFn: (values) => {
2879
+ const payload = {
2880
+ ...createInitialValues,
2881
+ ...values,
2882
+ ...relation && parentValue != null ? { [relation.foreignKey]: parentValue } : {}
2883
+ };
2884
+ if (tab.create?.endpoint) {
2885
+ return http.request({
2886
+ url: interpolateEndpoint(tab.create.endpoint, payload, parent),
2887
+ method: tab.create.method ?? "POST",
2888
+ data: payload
2889
+ }).then((response) => response.data);
2890
+ }
2891
+ return resource.create(payload);
2892
+ },
2860
2893
  onSuccess: async () => {
2861
2894
  await invalidate();
2862
2895
  setCreating(false);
@@ -4918,7 +4951,7 @@ function Sidebar({ app, menu, registry }) {
4918
4951
  // package.json
4919
4952
  var package_default = {
4920
4953
  name: "@oondemand/oon-core-front",
4921
- version: "0.4.2",
4954
+ version: "0.4.4",
4922
4955
  repository: {
4923
4956
  type: "git",
4924
4957
  url: "git+https://github.com/oondemand/oon-platform.git",
@@ -5105,6 +5138,8 @@ function CoreTopbar({ app, menu }) {
5105
5138
  const settingsHref = resolveSettingsHref(
5106
5139
  menu.filter((item) => hasPermission(item.permissions))
5107
5140
  );
5141
+ const tenant = user?.tenant;
5142
+ const tenantLabel = String(tenant?.nome || tenant?.slug || "").trim();
5108
5143
  return /* @__PURE__ */ jsxs22(
5109
5144
  Flex14,
5110
5145
  {
@@ -5134,6 +5169,10 @@ function CoreTopbar({ app, menu }) {
5134
5169
  }
5135
5170
  ),
5136
5171
  /* @__PURE__ */ jsx31(Text22, { fontSize: "sm", fontWeight: "600", color: "#24323A", children: app.title ?? app.name }),
5172
+ tenantLabel ? /* @__PURE__ */ jsxs22(Text22, { fontSize: "xs", fontWeight: "600", color: "brand.600", lineHeight: "1.4", children: [
5173
+ "Tenant: ",
5174
+ tenantLabel
5175
+ ] }) : null,
5137
5176
  /* @__PURE__ */ jsxs22(Text22, { fontSize: "10px", color: "gray.600", lineHeight: "1.35", children: [
5138
5177
  "Central ",
5139
5178
  centralVersion,
@@ -5374,11 +5413,23 @@ import {
5374
5413
  Flex as Flex16,
5375
5414
  Heading as Heading12,
5376
5415
  Input as Input8,
5416
+ SimpleGrid as SimpleGrid5,
5377
5417
  Text as Text24,
5378
5418
  VStack as VStack4
5379
5419
  } from "@chakra-ui/react";
5380
5420
  import { Navigate as Navigate2, useLocation as useLocation3 } from "react-router-dom";
5381
5421
  import { jsx as jsx35, jsxs as jsxs24 } from "react/jsx-runtime";
5422
+ function tenantOptions(error) {
5423
+ const details = error.details;
5424
+ if (!details || typeof details !== "object" || Array.isArray(details)) return [];
5425
+ const tenants = details.tenants;
5426
+ if (!Array.isArray(tenants)) return [];
5427
+ return tenants.filter((tenant) => Boolean(tenant) && typeof tenant === "object").map((tenant) => ({
5428
+ id: String(tenant.id || ""),
5429
+ nome: String(tenant.nome || tenant.slug || tenant.id || ""),
5430
+ slug: tenant.slug ? String(tenant.slug) : void 0
5431
+ })).filter((tenant) => tenant.id && tenant.nome);
5432
+ }
5382
5433
  function safeRedirect(search) {
5383
5434
  const value2 = new URLSearchParams(search).get("redirect") || "/";
5384
5435
  if (!value2.startsWith("/") || value2.startsWith("//")) return "/";
@@ -5394,6 +5445,8 @@ function LoginPage({ app }) {
5394
5445
  const [password, setPassword] = useState16("");
5395
5446
  const [submitting, setSubmitting] = useState16(false);
5396
5447
  const [message, setMessage] = useState16(null);
5448
+ const [tenants, setTenants] = useState16([]);
5449
+ const [tenantId, setTenantId] = useState16("");
5397
5450
  const redirect = safeRedirect(location.search);
5398
5451
  if (status === "authenticated") {
5399
5452
  return /* @__PURE__ */ jsx35(Navigate2, { to: redirect, replace: true });
@@ -5406,9 +5459,22 @@ function LoginPage({ app }) {
5406
5459
  setSubmitting(true);
5407
5460
  setMessage(null);
5408
5461
  try {
5409
- await login(email.trim(), password);
5462
+ if (tenants.length > 1 && !tenantId) {
5463
+ setMessage("Selecione a organiza\xE7\xE3o para continuar.");
5464
+ return;
5465
+ }
5466
+ await login(email.trim(), password, tenantId || void 0);
5410
5467
  } catch (error) {
5411
5468
  const authError = error;
5469
+ if (authError.code === "TENANT_SELECTION_REQUIRED") {
5470
+ const options = tenantOptions(authError);
5471
+ if (options.length > 1) {
5472
+ setTenants(options);
5473
+ setTenantId("");
5474
+ setMessage("Seu usu\xE1rio possui acesso a mais de uma organiza\xE7\xE3o. Selecione uma para continuar.");
5475
+ return;
5476
+ }
5477
+ }
5412
5478
  if (authError.status === 403) return;
5413
5479
  setMessage(
5414
5480
  authError.status === 401 ? "E-mail ou senha inv\xE1lidos." : authError.message || "N\xE3o foi poss\xEDvel entrar. Tente novamente."
@@ -5421,7 +5487,7 @@ function LoginPage({ app }) {
5421
5487
  Box23,
5422
5488
  {
5423
5489
  width: "100%",
5424
- maxW: "440px",
5490
+ maxW: tenants.length > 1 ? "680px" : "440px",
5425
5491
  bg: "white",
5426
5492
  borderWidth: "1px",
5427
5493
  borderColor: "#E4EBEF",
@@ -5452,7 +5518,11 @@ function LoginPage({ app }) {
5452
5518
  {
5453
5519
  type: "email",
5454
5520
  value: email,
5455
- onChange: (event) => setEmail(event.target.value),
5521
+ onChange: (event) => {
5522
+ setEmail(event.target.value);
5523
+ setTenants([]);
5524
+ setTenantId("");
5525
+ },
5456
5526
  autoComplete: "email",
5457
5527
  placeholder: "voce@empresa.com.br",
5458
5528
  required: true
@@ -5472,7 +5542,40 @@ function LoginPage({ app }) {
5472
5542
  required: true
5473
5543
  }
5474
5544
  )
5475
- ] })
5545
+ ] }),
5546
+ tenants.length > 1 ? /* @__PURE__ */ jsxs24(Box23, { children: [
5547
+ /* @__PURE__ */ jsx35(Text24, { mb: 3, fontSize: "sm", fontWeight: "600", color: "#24323A", children: "Escolha o tenant para acessar" }),
5548
+ /* @__PURE__ */ jsx35(SimpleGrid5, { columns: { base: 1, md: 2 }, gap: 3, children: tenants.map((tenant) => {
5549
+ const selected = tenantId === tenant.id;
5550
+ return /* @__PURE__ */ jsx35(
5551
+ Button16,
5552
+ {
5553
+ type: "button",
5554
+ variant: "outline",
5555
+ height: "auto",
5556
+ minH: "88px",
5557
+ justifyContent: "flex-start",
5558
+ whiteSpace: "normal",
5559
+ textAlign: "left",
5560
+ borderWidth: "2px",
5561
+ borderColor: selected ? "#39BFED" : "#D9E2E7",
5562
+ bg: selected ? "#EFFAFF" : "white",
5563
+ color: "#24323A",
5564
+ "aria-pressed": selected,
5565
+ onClick: () => {
5566
+ setTenantId(tenant.id);
5567
+ setMessage(null);
5568
+ },
5569
+ _hover: { borderColor: "#39BFED", bg: "#F6FCFE" },
5570
+ children: /* @__PURE__ */ jsxs24(VStack4, { align: "start", gap: 1, py: 3, children: [
5571
+ /* @__PURE__ */ jsx35(Text24, { fontWeight: "700", children: tenant.nome }),
5572
+ tenant.slug ? /* @__PURE__ */ jsx35(Text24, { fontSize: "xs", color: "gray.500", children: tenant.slug }) : null
5573
+ ] })
5574
+ },
5575
+ tenant.id
5576
+ );
5577
+ }) })
5578
+ ] }) : null
5476
5579
  ] }),
5477
5580
  message ? /* @__PURE__ */ jsx35(
5478
5581
  Box23,
@@ -5494,7 +5597,7 @@ function LoginPage({ app }) {
5494
5597
  bg: "#07262E",
5495
5598
  color: "white",
5496
5599
  _hover: { bg: "#0B3843" },
5497
- disabled: submitting || !email.trim() || !password,
5600
+ disabled: submitting || !email.trim() || !password || tenants.length > 1 && !tenantId,
5498
5601
  children: submitting ? "Validando acesso..." : "Entrar"
5499
5602
  }
5500
5603
  )