@umituz/web-dashboard 5.0.1 → 6.0.0

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.
Files changed (34) hide show
  1. package/package.json +2 -12
  2. package/src/index.ts +0 -10
  3. package/src/styles/dashboard.css +0 -460
  4. package/src/domains/auth/components/AuthLayout.tsx +0 -99
  5. package/src/domains/auth/components/ForgotPasswordForm.tsx +0 -167
  6. package/src/domains/auth/components/LoginForm.tsx +0 -222
  7. package/src/domains/auth/components/RegisterForm.tsx +0 -281
  8. package/src/domains/auth/components/ResetPasswordForm.tsx +0 -173
  9. package/src/domains/auth/components/index.ts +0 -11
  10. package/src/domains/auth/components/shared/AuthErrorBanner.tsx +0 -25
  11. package/src/domains/auth/components/shared/EmailInput.tsx +0 -73
  12. package/src/domains/auth/components/shared/PasswordInput.tsx +0 -100
  13. package/src/domains/auth/components/shared/PasswordStrengthIndicator.tsx +0 -63
  14. package/src/domains/auth/components/shared/SocialLoginButtons.tsx +0 -44
  15. package/src/domains/auth/components/shared/SocialProviderIcons.tsx +0 -48
  16. package/src/domains/auth/components/shared/index.ts +0 -20
  17. package/src/domains/auth/hooks/index.ts +0 -8
  18. package/src/domains/auth/hooks/useAuth.ts +0 -230
  19. package/src/domains/auth/index.ts +0 -62
  20. package/src/domains/auth/types/auth.ts +0 -302
  21. package/src/domains/auth/types/index.ts +0 -25
  22. package/src/domains/auth/utils/auth.test.ts +0 -188
  23. package/src/domains/auth/utils/auth.ts +0 -221
  24. package/src/domains/auth/utils/i18nKeys.ts +0 -109
  25. package/src/domains/auth/utils/index.ts +0 -29
  26. package/src/domains/auth/utils/secureToken.ts +0 -33
  27. package/src/domains/auth/utils/validationKeys.ts +0 -23
  28. package/src/domains/session/DemoLogin.tsx +0 -42
  29. package/src/domains/session/OrgSwitcher.tsx +0 -102
  30. package/src/domains/session/SessionContext.tsx +0 -150
  31. package/src/domains/session/inMemoryAuthAdapter.ts +0 -53
  32. package/src/domains/session/index.ts +0 -18
  33. package/src/domains/session/types.ts +0 -47
  34. package/src/domains/session/utils/i18nKeys.ts +0 -12
@@ -1,102 +0,0 @@
1
- import { useState } from "react";
2
- import { Building2, Check, ChevronDown } from "lucide-react";
3
- import { cn } from "@umituz/web-design-system/utils/cn";
4
- import { useT } from "@umituz/web-localization/react";
5
- import { SESSION_KEYS } from "./utils/i18nKeys";
6
- import type { Tenant } from "./types";
7
-
8
- export interface OrgSwitcherProps {
9
- tenants: Tenant[];
10
- activeTenantId?: string | null;
11
- onSwitch: (tenantId: string) => void;
12
- disabled?: boolean;
13
- className?: string;
14
- }
15
-
16
- const initialsOf = (tenant: Tenant): string =>
17
- tenant.initials ??
18
- tenant.name
19
- .split(/\s+/)
20
- .slice(0, 2)
21
- .map((word) => word.charAt(0).toUpperCase())
22
- .join("");
23
-
24
- export const OrgSwitcher = ({
25
- tenants,
26
- activeTenantId,
27
- onSwitch,
28
- disabled = false,
29
- className,
30
- }: OrgSwitcherProps) => {
31
- const t = useT();
32
- const [open, setOpen] = useState(false);
33
-
34
- if (tenants.length === 0) return null;
35
-
36
- const active = tenants.find((tenant) => tenant.id === activeTenantId) ?? tenants[0];
37
-
38
- return (
39
- <div className={cn("udb-header__anchor", className)}>
40
- <button
41
- type="button"
42
- className="udb-profile-btn"
43
- disabled={disabled}
44
- aria-haspopup="menu"
45
- aria-expanded={open}
46
- aria-label={t(SESSION_KEYS.orgSwitcher.label)}
47
- onClick={() => setOpen((prev) => !prev)}
48
- >
49
- <span className="udb-avatar">{initialsOf(active)}</span>
50
- <span className="udb-org">
51
- <span className="udb-org__name">{active.name}</span>
52
- {active.plan && <span className="udb-org__meta">{active.plan}</span>}
53
- </span>
54
- <ChevronDown
55
- className={cn(
56
- "udb-profile-btn__chevron",
57
- open && "udb-profile-btn__chevron--open",
58
- )}
59
- aria-hidden="true"
60
- />
61
- </button>
62
-
63
- {open && (
64
- <>
65
- <button
66
- type="button"
67
- className="udb-overlay"
68
- aria-label="close tenant menu"
69
- tabIndex={-1}
70
- onClick={() => setOpen(false)}
71
- />
72
- <div className="udb-menu udb-org-menu" role="menu">
73
- {tenants.map((tenant) => {
74
- const isActive = tenant.id === active.id;
75
- return (
76
- <button
77
- key={tenant.id}
78
- type="button"
79
- role="menuitem"
80
- className={cn(
81
- "udb-menu__item",
82
- isActive && "udb-menu__item--active",
83
- )}
84
- onClick={() => {
85
- onSwitch(tenant.id);
86
- setOpen(false);
87
- }}
88
- >
89
- <Building2 className="udb-menu__item-icon" aria-hidden="true" />
90
- <span className="udb-org-menu__label">{tenant.name}</span>
91
- {isActive && <Check className="udb-org-menu__check" aria-hidden="true" />}
92
- </button>
93
- );
94
- })}
95
- </div>
96
- </>
97
- )}
98
- </div>
99
- );
100
- };
101
-
102
- export default OrgSwitcher;
@@ -1,150 +0,0 @@
1
- /**
2
- * Session context — user + active tenant state over an injected
3
- * AuthAdapter. Restores the current user on mount, exposes
4
- * login/logout/demoLogin, loads tenants for the user and keeps an
5
- * `activeTenant` the OrgSwitcher drives via `switchTenant`.
6
- */
7
-
8
- import {
9
- createContext,
10
- useCallback,
11
- useContext,
12
- useEffect,
13
- useMemo,
14
- useState,
15
- type ReactNode,
16
- } from "react";
17
- import type {
18
- AuthAdapter,
19
- LoginCredentials,
20
- SessionStatus,
21
- SessionUser,
22
- Tenant,
23
- } from "./types";
24
-
25
- export interface SessionProviderProps {
26
- adapter: AuthAdapter;
27
- children: ReactNode;
28
- }
29
-
30
- interface SessionContextValue {
31
- user: SessionUser | null;
32
- tenants: Tenant[];
33
- activeTenant: Tenant | null;
34
- status: SessionStatus;
35
- login: (credentials: LoginCredentials) => Promise<SessionUser>;
36
- logout: () => Promise<void>;
37
- demoLogin: (userId: string) => Promise<SessionUser>;
38
- switchTenant: (tenantId: string) => void;
39
- }
40
-
41
- const SessionContext = createContext<SessionContextValue | null>(null);
42
-
43
- export const SessionProvider = ({ adapter, children }: SessionProviderProps) => {
44
- const [user, setUser] = useState<SessionUser | null>(null);
45
- const [tenants, setTenants] = useState<Tenant[]>([]);
46
- const [activeTenantId, setActiveTenantId] = useState<string | null>(null);
47
- const [status, setStatus] = useState<SessionStatus>("loading");
48
-
49
- // Restore the session once on mount.
50
- useEffect(() => {
51
- let cancelled = false;
52
- adapter
53
- .current()
54
- .then((current) => {
55
- if (cancelled) return;
56
- setUser(current);
57
- setStatus(current ? "authenticated" : "anonymous");
58
- })
59
- .catch(() => {
60
- if (!cancelled) setStatus("anonymous");
61
- });
62
- return () => {
63
- cancelled = true;
64
- };
65
- }, [adapter]);
66
-
67
- // Load tenants whenever the authenticated user changes.
68
- useEffect(() => {
69
- if (!user || !adapter.listTenants) {
70
- setTenants([]);
71
- setActiveTenantId(null);
72
- return;
73
- }
74
- let cancelled = false;
75
- adapter
76
- .listTenants(user.id)
77
- .then((list) => {
78
- if (cancelled) return;
79
- setTenants(list);
80
- setActiveTenantId((prev) =>
81
- prev && list.some((tenant) => tenant.id === prev) ? prev : (list[0]?.id ?? null),
82
- );
83
- })
84
- .catch(() => {
85
- if (!cancelled) setTenants([]);
86
- });
87
- return () => {
88
- cancelled = true;
89
- };
90
- }, [adapter, user]);
91
-
92
- const login = useCallback(
93
- async (credentials: LoginCredentials) => {
94
- const next = await adapter.login(credentials);
95
- setUser(next);
96
- setStatus("authenticated");
97
- return next;
98
- },
99
- [adapter],
100
- );
101
-
102
- const logout = useCallback(async () => {
103
- await adapter.logout();
104
- setUser(null);
105
- setStatus("anonymous");
106
- }, [adapter]);
107
-
108
- const demoLogin = useCallback(
109
- async (userId: string) => {
110
- if (!adapter.demoLogin) {
111
- throw new Error("AuthAdapter does not support demoLogin");
112
- }
113
- const next = await adapter.demoLogin(userId);
114
- setUser(next);
115
- setStatus("authenticated");
116
- return next;
117
- },
118
- [adapter],
119
- );
120
-
121
- const switchTenant = useCallback((tenantId: string) => {
122
- setActiveTenantId(tenantId);
123
- }, []);
124
-
125
- const value = useMemo<SessionContextValue>(
126
- () => ({
127
- user,
128
- tenants,
129
- activeTenant: tenants.find((tenant) => tenant.id === activeTenantId) ?? null,
130
- status,
131
- login,
132
- logout,
133
- demoLogin,
134
- switchTenant,
135
- }),
136
- [user, tenants, activeTenantId, status, login, logout, demoLogin, switchTenant],
137
- );
138
-
139
- return (
140
- <SessionContext.Provider value={value}>{children}</SessionContext.Provider>
141
- );
142
- };
143
-
144
- export const useSession = (): SessionContextValue => {
145
- const context = useContext(SessionContext);
146
- if (!context) {
147
- throw new Error("useSession requires a <SessionProvider> ancestor");
148
- }
149
- return context;
150
- };
@@ -1,53 +0,0 @@
1
- /**
2
- * In-memory auth adapter — zero-dependency default for demos,
3
- * Storybook-style setups and tests. Credentials are NOT checked
4
- * (any password logs in); demo accounts log in by user id.
5
- */
6
-
7
- import type {
8
- AuthAdapter,
9
- LoginCredentials,
10
- SessionUser,
11
- } from "./types";
12
-
13
- export interface InMemoryAuthAdapterOptions {
14
- /** Users available for login/demo. */
15
- users: readonly SessionUser[];
16
- }
17
-
18
- export const createInMemoryAuthAdapter = ({
19
- users,
20
- }: InMemoryAuthAdapterOptions): AuthAdapter => {
21
- let currentUser: SessionUser | null = null;
22
-
23
- const findByEmail = (email: string): SessionUser | undefined =>
24
- users.find((user) => user.email === email);
25
-
26
- return {
27
- async login({ email }: LoginCredentials): Promise<SessionUser> {
28
- const user = findByEmail(email);
29
- if (!user) {
30
- throw new Error(`No user with email ${email}`);
31
- }
32
- currentUser = user;
33
- return user;
34
- },
35
-
36
- async logout(): Promise<void> {
37
- currentUser = null;
38
- },
39
-
40
- async current(): Promise<SessionUser | null> {
41
- return currentUser;
42
- },
43
-
44
- async demoLogin(userId: string): Promise<SessionUser> {
45
- const user = users.find((candidate) => candidate.id === userId);
46
- if (!user) {
47
- throw new Error(`No demo user with id ${userId}`);
48
- }
49
- currentUser = user;
50
- return user;
51
- },
52
- };
53
- };
@@ -1,18 +0,0 @@
1
- export {
2
- SessionProvider,
3
- useSession,
4
- type SessionProviderProps,
5
- } from "./SessionContext";
6
- export { OrgSwitcher, type OrgSwitcherProps } from "./OrgSwitcher";
7
- export { DemoLogin, type DemoLoginProps } from "./DemoLogin";
8
- export { createInMemoryAuthAdapter, type InMemoryAuthAdapterOptions } from "./inMemoryAuthAdapter";
9
- export {
10
- SESSION_KEYS,
11
- } from "./utils/i18nKeys";
12
- export type {
13
- AuthAdapter,
14
- LoginCredentials,
15
- SessionStatus,
16
- SessionUser,
17
- Tenant,
18
- } from "./types";
@@ -1,47 +0,0 @@
1
- /**
2
- * Session model — the host-facing auth contract.
3
- *
4
- * The package performs no authentication itself; hosts adapt their
5
- * provider (Firebase, Supabase, NEON-backed REST, mock...) to this
6
- * interface and hand it to <SessionProvider>.
7
- */
8
-
9
- /** Authenticated user as the dashboard presents it. */
10
- export interface SessionUser {
11
- id: string;
12
- name: string;
13
- email: string;
14
- /** Host role key — pairs with the permissions matrix. */
15
- role: string;
16
- avatarUrl?: string;
17
- }
18
-
19
- /** Tenant/organization the user may switch between. */
20
- export interface Tenant {
21
- id: string;
22
- name: string;
23
- /** Short initials for avatar-style rendering. */
24
- initials?: string;
25
- /** Optional plan badge (e.g. "starter" | "growth" | "enterprise"). */
26
- plan?: string;
27
- }
28
-
29
- export interface LoginCredentials {
30
- email: string;
31
- password: string;
32
- }
33
-
34
- /**
35
- * Auth adapter seam. `login`/`logout`/`current` are required;
36
- * `listTenants` and `demoLogin` power the multi-tenant switcher and
37
- * one-click demo entry when the host supports them.
38
- */
39
- export interface AuthAdapter {
40
- login(credentials: LoginCredentials): Promise<SessionUser>;
41
- logout(): Promise<void>;
42
- current(): Promise<SessionUser | null>;
43
- listTenants?(userId: string): Promise<Tenant[]>;
44
- demoLogin?(userId: string): Promise<SessionUser>;
45
- }
46
-
47
- export type SessionStatus = "loading" | "authenticated" | "anonymous";
@@ -1,12 +0,0 @@
1
- /**
2
- * Session domain i18n keys — the documented string contract.
3
- */
4
-
5
- export const SESSION_KEYS = {
6
- orgSwitcher: {
7
- label: "session.orgSwitcher.label",
8
- },
9
- demoLogin: {
10
- action: "session.demoLogin.action",
11
- },
12
- } as const;