@fayz-ai/portal 0.9.0 → 0.11.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 (39) hide show
  1. package/dist/{createMemberApp.d.ts → app/createMemberApp.d.ts} +1 -1
  2. package/dist/app/createMemberApp.d.ts.map +1 -0
  3. package/dist/app/router.d.ts.map +1 -0
  4. package/dist/{scaffold.d.ts → app/scaffold.d.ts} +1 -1
  5. package/dist/app/scaffold.d.ts.map +1 -0
  6. package/dist/data/auth.d.ts.map +1 -0
  7. package/dist/data/session.d.ts.map +1 -0
  8. package/dist/index.d.ts +10 -10
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -1
  11. package/dist/index.js.map +1 -1
  12. package/dist/views/AccountPage.d.ts.map +1 -0
  13. package/dist/{pages → views}/CoursePlayerPage.d.ts.map +1 -1
  14. package/dist/views/MyCoursesPage.d.ts.map +1 -0
  15. package/package.json +5 -7
  16. package/dist/auth.d.ts.map +0 -1
  17. package/dist/createMemberApp.d.ts.map +0 -1
  18. package/dist/pages/AccountPage.d.ts.map +0 -1
  19. package/dist/pages/MyCoursesPage.d.ts.map +0 -1
  20. package/dist/router.d.ts.map +0 -1
  21. package/dist/scaffold.d.ts.map +0 -1
  22. package/dist/session.d.ts.map +0 -1
  23. package/src/auth.ts +0 -98
  24. package/src/components/MemberHeader.tsx +0 -57
  25. package/src/config.ts +0 -39
  26. package/src/createMemberApp.tsx +0 -66
  27. package/src/index.ts +0 -56
  28. package/src/pages/AccountPage.tsx +0 -39
  29. package/src/pages/CoursePlayerPage.tsx +0 -126
  30. package/src/pages/MyCoursesPage.tsx +0 -117
  31. package/src/router.ts +0 -45
  32. package/src/scaffold.tsx +0 -73
  33. package/src/session.ts +0 -26
  34. /package/dist/{router.d.ts → app/router.d.ts} +0 -0
  35. /package/dist/{auth.d.ts → data/auth.d.ts} +0 -0
  36. /package/dist/{session.d.ts → data/session.d.ts} +0 -0
  37. /package/dist/{pages → views}/AccountPage.d.ts +0 -0
  38. /package/dist/{pages → views}/CoursePlayerPage.d.ts +0 -0
  39. /package/dist/{pages → views}/MyCoursesPage.d.ts +0 -0
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { MemberConfig } from './config';
2
+ import type { MemberConfig } from '../config';
3
3
  /** Side-effect runtime init shared by the factory and the manifest scaffold:
4
4
  * wires learner auth (+ optional Supabase client). Idempotent. */
5
5
  export declare function initMemberRuntime(config: MemberConfig): void;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createMemberApp.d.ts","sourceRoot":"","sources":["../../src/app/createMemberApp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAsB7C;mEACmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAQ5D;AAED;8DAC8D;AAC9D,wBAAgB,WAAW,sBAW1B;AAED;2CAC2C;AAC3C,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,KAAK,CAAC,aAAa,CAYzE"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/app/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAQlD,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,WAAW,IAAI,MAAM,CASpC;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAatF;AAED,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAC9E,EAAE,EAAE,MAAM,CAAA;CACX;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAExD"}
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { AppManifest } from '@fayz-ai/core';
3
- import type { MemberConfig } from './config';
3
+ import type { MemberConfig } from '../config';
4
4
  export declare function defineMember(config: MemberConfig): AppManifest;
5
5
  export declare function MemberScaffold({ manifest, surface }: {
6
6
  manifest: AppManifest;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/app/scaffold.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAc7C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAiB9D;AAcD,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,qBAa/F;yBAbe,cAAc;;;AAmB9B,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/data/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,eAAe,CAAA;AAgB1D;gDACgD;AAChD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAKD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,QAAQ,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,WAAW,CAMb;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAY1F;AAsBD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAQjC;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAMjH;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMnD"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/data/session.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,UAAU,CAAC,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAChF,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;EAW5B,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,19 +1,19 @@
1
1
  export type { MemberConfig, ResolvedMemberConfig } from './config';
2
2
  export { MemberConfigProvider, useMemberConfig, resolveConfig } from './config';
3
- export { createMemberApp, initMemberRuntime, MemberShell } from './createMemberApp';
4
- export { defineMember, MemberScaffold } from './scaffold';
5
- export { useMemberSession } from './session';
6
- export type { MemberSessionState } from './session';
7
- export { initMemberAuth, establishMemberSession, signUpMember, signOutMember, customerIdForEmail, resolveAuthAdapter, } from './auth';
8
- export { useHashPath, matchPath, navigateTo, Link } from './router';
9
- export { MyCoursesPage } from './pages/MyCoursesPage';
10
- export { CoursePlayerPage } from './pages/CoursePlayerPage';
11
- export { AccountPage } from './pages/AccountPage';
3
+ export { createMemberApp, initMemberRuntime, MemberShell } from './app/createMemberApp';
4
+ export { defineMember, MemberScaffold } from './app/scaffold';
5
+ export { useMemberSession } from './data/session';
6
+ export type { MemberSessionState } from './data/session';
7
+ export { initMemberAuth, establishMemberSession, signUpMember, signOutMember, customerIdForEmail, resolveAuthAdapter, } from './data/auth';
8
+ export { useHashPath, matchPath, navigateTo, Link } from './app/router';
9
+ export { MyCoursesPage } from './views/MyCoursesPage';
10
+ export { CoursePlayerPage } from './views/CoursePlayerPage';
11
+ export { AccountPage } from './views/AccountPage';
12
12
  export { MemberHeader } from './components/MemberHeader';
13
13
  import { renderApp as coreRenderApp } from '@fayz-ai/core';
14
14
  export declare const renderApp: typeof coreRenderApp;
15
15
  export { defineApp } from '@fayz-ai/core';
16
- export { registerMemberScaffold } from './scaffold';
16
+ export { registerMemberScaffold } from './app/scaffold';
17
17
  export { setCoursesProvider, getCoursesProvider, getCoursesProviderOptional, MockCoursesProvider, createMockCoursesProvider, useMyCourses, useCourse, useCourseProgress, } from '@fayz-ai/courses';
18
18
  export type { CoursesProvider, MockCoursesSeed } from '@fayz-ai/courses';
19
19
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACnF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAC5C,YAAY,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,QAAQ,CAAA;AAEf,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAEnE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAUxD,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,eAAe,CAAA;AAG1D,eAAO,MAAM,SAAS,EAAE,OAAO,aAG9B,CAAA;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,YAAY,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAClE,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE/E,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACvF,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAE7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAExD,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,YAAY,EACZ,aAAa,EACb,kBAAkB,EAClB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEvE,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAUxD,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,eAAe,CAAA;AAG1D,eAAO,MAAM,SAAS,EAAE,OAAO,aAG9B,CAAA;AAED,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AACvD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,mBAAmB,EACnB,yBAAyB,EACzB,YAAY,EACZ,SAAS,EACT,iBAAiB,GAClB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA"}
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ var useMemberSession = create()(
32
32
  )
33
33
  );
34
34
 
35
- // src/auth.ts
35
+ // src/data/auth.ts
36
36
  function slug(s) {
37
37
  return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
38
38
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts","../src/session.ts","../src/auth.ts","../src/router.ts","../src/components/MemberHeader.tsx","../src/pages/MyCoursesPage.tsx","../src/pages/CoursePlayerPage.tsx","../src/pages/AccountPage.tsx","../src/createMemberApp.tsx","../src/scaffold.tsx","../src/index.ts"],"names":["React","adapter","useState","jsxs","jsx","slug","getCoursesProvider","coreRenderApp"],"mappings":"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","file":"index.js","sourcesContent":["import React from 'react'\n\n// ---------------------------------------------------------------------------\n// MemberConfig — the declarative surface for the learner portal. Pure data so\n// it round-trips through an AppManifest (defineMember ↔ MemberScaffold).\n// ---------------------------------------------------------------------------\n\nexport interface MemberConfig {\n name: string\n /** Logo image URL shown in the header (falls back to the name). */\n logoUrl?: string\n locale?: string\n /** Accent color (CSS color) for the player progress + active states. */\n accent?: string\n /** Auth adapter: 'mock' (default) | 'supabase' | a bring-your-own adapter. */\n auth?: { adapter?: 'mock' | 'supabase' | unknown }\n supabaseUrl?: string\n supabaseAnonKey?: string\n /** Grant the logged-in learner every published course (demo behaviour). When\n * false, the learner only sees the courses they were explicitly enrolled in. */\n autoEnroll?: boolean\n}\n\nexport interface ResolvedMemberConfig extends MemberConfig {\n locale: string\n}\n\nexport function resolveConfig(config: MemberConfig): ResolvedMemberConfig {\n return { ...config, locale: config.locale ?? 'pt-BR' }\n}\n\nconst MemberConfigContext = React.createContext<ResolvedMemberConfig | null>(null)\nexport const MemberConfigProvider = MemberConfigContext.Provider\n\nexport function useMemberConfig(): ResolvedMemberConfig {\n const ctx = React.useContext(MemberConfigContext)\n if (!ctx) throw new Error('useMemberConfig must be used inside a MemberConfigProvider')\n return ctx\n}\n","import { create } from 'zustand'\nimport { persist } from 'zustand/middleware'\n\n// Learner session — mirrors the auth identity for synchronous UI access. The\n// customerId is the key courses enrollments/progress are stored against.\n\nexport interface MemberSessionState {\n customerId: string | null\n email: string | null\n name: string | null\n setSession(s: { customerId: string; email: string; name?: string | null }): void\n signOut(): void\n}\n\nexport const useMemberSession = create<MemberSessionState>()(\n persist(\n (set) => ({\n customerId: null,\n email: null,\n name: null,\n setSession: ({ customerId, email, name }) => set({ customerId, email, name: name ?? null }),\n signOut: () => set({ customerId: null, email: null, name: null }),\n }),\n { name: 'fayz.portal.session.v1' },\n ),\n)\n","import type { AuthAdapter, AuthUser } from '@fayz-ai/core'\nimport { createMockAuthAdapter, createSupabaseAuthAdapter } from '@fayz-ai/auth'\nimport { getCoursesProvider } from '@fayz-ai/courses'\nimport { useMemberSession } from './session'\n\n// ---------------------------------------------------------------------------\n// Learner auth — the SAME AuthAdapter contract the admin + storefront use. On\n// top of the auth identity, the portal derives a stable customerId (the key\n// enrollments/progress hang off) and, in demo mode, grants the learner every\n// published course (so any login lands in a populated \"my courses\").\n// ---------------------------------------------------------------------------\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n}\n\n/** Deterministic learner id from an email, so the same login always maps to the\n * same enrollments/progress across reloads. */\nexport function customerIdForEmail(email: string): string {\n return `cust-${slug(email.trim().toLowerCase())}`\n}\n\nlet _adapter: AuthAdapter | null = null\nlet _autoEnroll = true\n\nexport function resolveAuthAdapter(\n configured: unknown,\n supabase?: { url?: string; anonKey?: string },\n): AuthAdapter {\n if (configured && configured !== 'mock' && configured !== 'supabase') return configured as AuthAdapter\n if (configured === 'supabase' && supabase?.url && supabase?.anonKey) {\n return createSupabaseAuthAdapter({ supabaseUrl: supabase.url, supabaseAnonKey: supabase.anonKey })\n }\n return createMockAuthAdapter()\n}\n\nexport function initMemberAuth(adapter: AuthAdapter, opts?: { autoEnroll?: boolean }): void {\n _adapter = adapter\n _autoEnroll = opts?.autoEnroll ?? true\n adapter\n .getSession()\n .then((existing) => {\n if (existing) void link(existing.user)\n })\n .catch(() => {})\n adapter.onAuthStateChange((user) => {\n if (!user) useMemberSession.getState().signOut()\n })\n}\n\nfunction getAdapter(): AuthAdapter {\n if (!_adapter) _adapter = createMockAuthAdapter()\n return _adapter\n}\n\n/** Enroll the learner in every published course — demo entitlement. Idempotent. */\nasync function grantAllPublished(customerId: string): Promise<void> {\n const provider = getCoursesProvider()\n const courses = await provider.listCourses({ status: 'published' })\n for (const c of courses) await provider.enroll(c.id, customerId)\n}\n\nasync function link(user: AuthUser, fullName?: string): Promise<{ customerId: string }> {\n const email = (user.email ?? '').trim().toLowerCase()\n const customerId = customerIdForEmail(email)\n if (_autoEnroll) await grantAllPublished(customerId)\n useMemberSession.getState().setSession({ customerId, email, name: fullName ?? user.fullName ?? null })\n return { customerId }\n}\n\nexport async function establishMemberSession(\n email: string,\n opts?: { password?: string; name?: string },\n): Promise<{ customerId: string }> {\n const adapter = getAdapter()\n const normalized = email.trim().toLowerCase()\n const session = opts?.password\n ? await adapter.signIn(normalized, opts.password)\n : await adapter.signUp(normalized, `member-${normalized}`, opts?.name ?? '')\n const user = session.user.email ? session.user : { ...session.user, email: normalized }\n return link(user, opts?.name)\n}\n\nexport async function signUpMember(email: string, password: string, name: string): Promise<{ customerId: string }> {\n const adapter = getAdapter()\n const normalized = email.trim().toLowerCase()\n const session = await adapter.signUp(normalized, password, name)\n const user = session.user.email ? session.user : { ...session.user, email: normalized }\n return link(user, name)\n}\n\nexport async function signOutMember(): Promise<void> {\n try {\n await getAdapter().signOut()\n } finally {\n useMemberSession.getState().signOut()\n }\n}\n","import React, { useEffect, useState } from 'react'\nimport { hashRouterAdapter } from '@fayz-ai/core'\n\n// Minimal hash router for the member portal — the same lightweight approach the\n// storefront uses (no react-router; avoids singleton hazards under source-alias).\n\nconst adapter = hashRouterAdapter()\n\nexport function navigateTo(to: string): void {\n adapter.navigate(to)\n}\n\nexport function useHashPath(): string {\n const [path, setPath] = useState(() => adapter.getCurrentPath() || '/')\n useEffect(() => {\n return adapter.onPathChange((p) => {\n setPath(p || '/')\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\nexport interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {\n to: string\n}\n\nexport function Link({ to, children, ...rest }: LinkProps) {\n return React.createElement('a', { href: `#${to}`, ...rest }, children)\n}\n","import React from 'react'\nimport { useMemberConfig } from '../config'\nimport { useMemberSession } from '../session'\nimport { signOutMember } from '../auth'\nimport { Link, navigateTo } from '../router'\n\nexport function MemberHeader() {\n const config = useMemberConfig()\n const session = useMemberSession()\n const [menuOpen, setMenuOpen] = React.useState(false)\n\n return (\n <header className=\"sticky top-0 z-30 border-b border-border bg-card/95 backdrop-blur\">\n <div className=\"mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6\">\n <Link to=\"/\" className=\"flex items-center gap-2 font-bold text-foreground\" data-testid=\"member-logo\">\n {config.logoUrl ? (\n <img src={config.logoUrl} alt={config.name} className=\"h-7\" />\n ) : (\n <span>{config.name}</span>\n )}\n </Link>\n\n {session.customerId ? (\n <div className=\"relative\">\n <button\n data-testid=\"member-usermenu\"\n onClick={() => setMenuOpen((v) => !v)}\n className=\"flex items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted\"\n >\n <span className=\"flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground\">\n {(session.email ?? '?').charAt(0).toUpperCase()}\n </span>\n <span className=\"hidden sm:inline\">{session.name || session.email}</span>\n </button>\n {menuOpen && (\n <div className=\"absolute right-0 mt-2 w-44 overflow-hidden rounded-lg border border-border bg-card shadow-lg\">\n <button\n onClick={() => { setMenuOpen(false); navigateTo('/account') }}\n className=\"block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted\"\n >\n Minha conta\n </button>\n <button\n data-testid=\"member-header-signout\"\n onClick={() => { setMenuOpen(false); void signOutMember(); navigateTo('/') }}\n className=\"block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted\"\n >\n Sair\n </button>\n </div>\n )}\n </div>\n ) : null}\n </div>\n </header>\n )\n}\n","import React, { useState } from 'react'\nimport { useMyCourses } from '@fayz-ai/courses'\nimport { useMemberSession } from '../session'\nimport { establishMemberSession } from '../auth'\nimport { useMemberConfig } from '../config'\nimport { navigateTo } from '../router'\n\nfunction AuthForm() {\n const config = useMemberConfig()\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n async function submit(e: React.FormEvent) {\n e.preventDefault()\n setError(null)\n if (!email.trim() || !password) return\n setBusy(true)\n try {\n await establishMemberSession(email, { password })\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Não foi possível entrar.')\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div className=\"mx-auto max-w-sm rounded-xl border border-border bg-card p-7 shadow-sm\">\n <h2 className=\"text-xl font-semibold text-foreground\">{config.name}</h2>\n <p className=\"mt-1 text-sm text-muted-foreground\">Entre para acessar seus cursos.</p>\n <form className=\"mt-5 space-y-3\" onSubmit={submit}>\n <input\n data-testid=\"member-email\"\n type=\"email\"\n required\n placeholder=\"voce@exemplo.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm\"\n />\n <input\n data-testid=\"member-password\"\n type=\"password\"\n required\n placeholder=\"Senha\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm\"\n />\n {error && <p data-testid=\"member-auth-error\" className=\"text-sm text-destructive\">{error}</p>}\n <button\n type=\"submit\"\n data-testid=\"member-signin\"\n disabled={busy}\n className=\"w-full rounded-md bg-primary py-2.5 font-semibold text-primary-foreground disabled:opacity-60\"\n >\n {busy ? 'Entrando…' : 'Entrar'}\n </button>\n </form>\n </div>\n )\n}\n\nexport function MyCoursesPage() {\n const session = useMemberSession()\n const { courses, loading } = useMyCourses(session.customerId)\n\n if (!session.customerId) {\n return (\n <main className=\"mx-auto max-w-3xl px-4 py-12 sm:px-6\">\n <AuthForm />\n </main>\n )\n }\n\n return (\n <main className=\"mx-auto max-w-5xl px-4 py-8 sm:px-6\">\n <h1 className=\"mb-1 text-2xl font-bold tracking-tight text-foreground\">Meus cursos</h1>\n <p className=\"mb-6 text-sm text-muted-foreground\">{session.email}</p>\n\n {loading ? (\n <div className=\"py-16 text-center text-muted-foreground\">Carregando…</div>\n ) : courses.length === 0 ? (\n <div data-testid=\"member-empty\" className=\"rounded-xl border border-border bg-card py-16 text-center text-muted-foreground\">\n Você ainda não tem cursos.\n </div>\n ) : (\n <div data-testid=\"member-courses\" className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {courses.map(({ course, progressPercent, completedLessons, totalLessons }) => (\n <button\n key={course.id}\n data-testid=\"member-course-card\"\n data-slug={course.slug}\n onClick={() => navigateTo(`/course/${course.slug}`)}\n className=\"group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md\"\n >\n <div className=\"aspect-video w-full overflow-hidden bg-muted\">\n {course.thumbnailUrl && <img src={course.thumbnailUrl} alt=\"\" className=\"h-full w-full object-cover\" />}\n </div>\n <div className=\"space-y-2 p-4\">\n <h3 className=\"font-semibold text-foreground line-clamp-2\">{course.title}</h3>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-primary transition-all\" style={{ width: `${progressPercent}%` }} />\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {completedLessons}/{totalLessons} aulas · {progressPercent}%\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </main>\n )\n}\n","import React from 'react'\nimport { useCourse, useCourseProgress, getCoursesProvider } from '@fayz-ai/courses'\nimport { useMemberSession } from '../session'\nimport { navigateTo } from '../router'\n\nexport function CoursePlayerPage({ slug, lessonId }: { slug: string; lessonId?: string }) {\n const session = useMemberSession()\n const { course, modules, lessons, loading } = useCourse(slug)\n const [enrollmentId, setEnrollmentId] = React.useState<string | null>(null)\n\n // Resolve this learner's enrollment for the course → drives progress.\n React.useEffect(() => {\n let active = true\n if (!course || !session.customerId) return\n ;(async () => {\n const provider = getCoursesProvider()\n const enrollments = await provider.listEnrollments(session.customerId!)\n let enrollment = enrollments.find((e) => e.courseId === course.id)\n if (!enrollment) enrollment = await provider.enroll(course.id, session.customerId!)\n if (active) setEnrollmentId(enrollment.id)\n })()\n return () => { active = false }\n }, [course, session.customerId])\n\n const { isCompleted, markLessonComplete } = useCourseProgress(enrollmentId)\n\n if (loading || !course) {\n return <main className=\"px-4 py-12 text-center text-muted-foreground\">Carregando…</main>\n }\n\n // Flatten lessons in true curriculum order (module order, then lesson order).\n // The provider sorts lessons by their per-module sortOrder, so a plain flat\n // list interleaves modules — walk modules to get the real sequence.\n const orderedLessons = modules.flatMap((m) => lessons.filter((l) => l.moduleId === m.id))\n\n const activeLesson = (lessonId && orderedLessons.find((l) => l.id === lessonId)) || orderedLessons[0]\n if (!activeLesson) {\n return <main className=\"px-4 py-12 text-center text-muted-foreground\">Curso sem aulas ainda.</main>\n }\n\n const idx = orderedLessons.findIndex((l) => l.id === activeLesson.id)\n const nextLesson = idx >= 0 && idx < orderedLessons.length - 1 ? orderedLessons[idx + 1] : null\n\n function open(lid: string) {\n navigateTo(`/course/${course!.slug}/lesson/${lid}`)\n }\n\n async function completeAndAdvance() {\n await markLessonComplete(activeLesson!.id, true)\n if (nextLesson) open(nextLesson.id)\n }\n\n return (\n <main className=\"mx-auto grid max-w-6xl grid-cols-1 gap-6 px-4 py-6 sm:px-6 lg:grid-cols-[1fr_320px]\">\n {/* Player + lesson body */}\n <div>\n <button onClick={() => navigateTo('/')} className=\"mb-3 text-sm text-muted-foreground hover:text-foreground\">\n ← Meus cursos\n </button>\n <div className=\"aspect-video w-full overflow-hidden rounded-xl bg-black\">\n <iframe\n key={activeLesson.id}\n data-testid=\"lesson-video\"\n src={activeLesson.videoUrl}\n title={activeLesson.title}\n className=\"h-full w-full\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n />\n </div>\n <div className=\"mt-4 flex items-start justify-between gap-4\">\n <div>\n <p className=\"text-xs text-muted-foreground\">{course.title}</p>\n <h1 data-testid=\"lesson-title\" className=\"text-xl font-bold text-foreground\">{activeLesson.title}</h1>\n </div>\n <button\n data-testid=\"mark-complete\"\n onClick={completeAndAdvance}\n className={`shrink-0 rounded-md px-4 py-2 text-sm font-semibold ${\n isCompleted(activeLesson.id)\n ? 'border border-border bg-card text-muted-foreground'\n : 'bg-primary text-primary-foreground'\n }`}\n >\n {isCompleted(activeLesson.id) ? '✓ Concluída' : 'Marcar como concluída'}\n </button>\n </div>\n {activeLesson.description && (\n <p className=\"mt-3 text-sm text-muted-foreground\">{activeLesson.description}</p>\n )}\n </div>\n\n {/* Curriculum sidebar */}\n <aside data-testid=\"lesson-sidebar\" className=\"rounded-xl border border-border bg-card p-3\">\n {modules.map((mod) => (\n <div key={mod.id} className=\"mb-3\">\n <p className=\"px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">{mod.title}</p>\n <ul className=\"space-y-0.5\">\n {lessons.filter((l) => l.moduleId === mod.id).map((lesson) => {\n const done = isCompleted(lesson.id)\n const current = lesson.id === activeLesson.id\n return (\n <li key={lesson.id}>\n <button\n data-testid=\"sidebar-lesson\"\n data-completed={done ? 'true' : 'false'}\n onClick={() => open(lesson.id)}\n className={`flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm ${\n current ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:bg-muted/60'\n }`}\n >\n <span className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full border text-[10px] ${\n done ? 'border-primary bg-primary text-primary-foreground' : 'border-border'\n }`}>{done ? '✓' : ''}</span>\n <span className=\"line-clamp-2\">{lesson.title}</span>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </aside>\n </main>\n )\n}\n","import React from 'react'\nimport { useMemberSession } from '../session'\nimport { signOutMember } from '../auth'\nimport { navigateTo } from '../router'\n\nexport function AccountPage() {\n const session = useMemberSession()\n\n if (!session.customerId) {\n navigateTo('/')\n return null\n }\n\n return (\n <main className=\"mx-auto max-w-xl px-4 py-10 sm:px-6\">\n <h1 className=\"mb-6 text-2xl font-bold text-foreground\">Minha conta</h1>\n <div className=\"space-y-4 rounded-xl border border-border bg-card p-6\">\n <Row label=\"Nome\" value={session.name || '—'} />\n <Row label=\"E-mail\" value={session.email || '—'} />\n <button\n data-testid=\"account-signout\"\n onClick={() => { void signOutMember(); navigateTo('/') }}\n className=\"mt-2 rounded-md border border-border bg-card px-4 py-2 text-sm font-medium text-foreground hover:bg-muted\"\n >\n Sair\n </button>\n </div>\n </main>\n )\n}\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex justify-between border-b border-border pb-3 last:border-0 last:pb-0\">\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n <span className=\"text-sm font-medium text-foreground\">{value}</span>\n </div>\n )\n}\n","import React from 'react'\nimport { MemberConfigProvider, resolveConfig, useMemberConfig } from './config'\nimport type { MemberConfig } from './config'\nimport { initMemberAuth, resolveAuthAdapter } from './auth'\nimport { useHashPath, matchPath } from './router'\nimport { MemberHeader } from './components/MemberHeader'\nimport { MyCoursesPage } from './pages/MyCoursesPage'\nimport { CoursePlayerPage } from './pages/CoursePlayerPage'\nimport { AccountPage } from './pages/AccountPage'\n\nfunction RouteSwitch() {\n const path = useHashPath()\n\n const lesson = matchPath('/course/:slug/lesson/:id', path)\n if (lesson?.slug) return <CoursePlayerPage slug={lesson.slug} lessonId={lesson.id} />\n\n const course = matchPath('/course/:slug', path)\n if (course?.slug) return <CoursePlayerPage slug={course.slug} />\n\n if (matchPath('/account', path)) return <AccountPage />\n\n return <MyCoursesPage />\n}\n\n/** Side-effect runtime init shared by the factory and the manifest scaffold:\n * wires learner auth (+ optional Supabase client). Idempotent. */\nexport function initMemberRuntime(config: MemberConfig): void {\n // The courses data provider is owned by the host app (mock or Supabase). The\n // portal only wires learner auth here; a Supabase-backed app sets the global\n // client + provider before mounting.\n initMemberAuth(\n resolveAuthAdapter(config.auth?.adapter, { url: config.supabaseUrl, anonKey: config.supabaseAnonKey }),\n { autoEnroll: config.autoEnroll ?? true },\n )\n}\n\n/** Inner portal UI — reads everything from config context, so it is shared by\n * createMemberApp and the manifest-driven MemberScaffold. */\nexport function MemberShell() {\n const config = useMemberConfig()\n return (\n <div className=\"min-h-screen bg-background text-foreground\" data-portal={config.name}>\n {config.accent && (\n <style>{`:root{--primary:${config.accent};}`}</style>\n )}\n <MemberHeader />\n <RouteSwitch />\n </div>\n )\n}\n\n/** Declarative member-portal factory — sugar over the manifest path (equivalent\n * to renderApp(defineMember(config))). */\nexport function createMemberApp(config: MemberConfig): React.ComponentType {\n const resolved = resolveConfig(config)\n initMemberRuntime(config)\n function MemberApp() {\n return (\n <MemberConfigProvider value={resolved}>\n <MemberShell />\n </MemberConfigProvider>\n )\n }\n MemberApp.displayName = 'MemberApp'\n return MemberApp\n}\n","import React from 'react'\nimport { registerScaffold, defineApp } from '@fayz-ai/core'\nimport type { AppManifest } from '@fayz-ai/core'\nimport { MemberConfigProvider, resolveConfig } from './config'\nimport type { MemberConfig } from './config'\nimport { initMemberRuntime, MemberShell } from './createMemberApp'\n\n// ---------------------------------------------------------------------------\n// Member scaffold — renders the learner portal from a pure-data AppManifest.\n// Mirrors the storefront scaffold's dual path: defineMember (config → manifest)\n// and MemberScaffold (manifest → render). This is the SDK's third surface type\n// (authenticated member area), alongside 'admin' and 'storefront'.\n// ---------------------------------------------------------------------------\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'app'\n}\n\nexport function defineMember(config: MemberConfig): AppManifest {\n return defineApp({\n id: slug(config.name),\n name: config.name,\n backend: config.supabaseUrl ? { provider: 'supabase', url: config.supabaseUrl } : { provider: 'mock' },\n locale: { default: config.locale ?? 'pt-BR', supported: [config.locale ?? 'pt-BR'] },\n surfaces: {\n member: {\n scaffold: 'member',\n options: {\n logoUrl: config.logoUrl,\n accent: config.accent,\n autoEnroll: config.autoEnroll ?? true,\n },\n },\n },\n })\n}\n\nfunction manifestToMemberConfig(manifest: AppManifest, surfaceName: string): MemberConfig {\n const o = (manifest.surfaces[surfaceName]?.options ?? {}) as Record<string, unknown>\n return {\n name: manifest.name,\n locale: (manifest.locale as { default?: string } | undefined)?.default,\n logoUrl: o.logoUrl as string | undefined,\n accent: o.accent as string | undefined,\n autoEnroll: (o.autoEnroll as boolean | undefined) ?? true,\n supabaseUrl: manifest.backend?.provider === 'supabase' ? manifest.backend.url : undefined,\n }\n}\n\nexport function MemberScaffold({ manifest, surface }: { manifest: AppManifest; surface: string }) {\n const config = React.useMemo(() => manifestToMemberConfig(manifest, surface), [manifest, surface])\n const resolved = React.useMemo(() => resolveConfig(config), [config])\n const inited = React.useRef(false)\n if (!inited.current) {\n initMemberRuntime(config)\n inited.current = true\n }\n return (\n <MemberConfigProvider value={resolved}>\n <MemberShell />\n </MemberConfigProvider>\n )\n}\nMemberScaffold.displayName = 'MemberScaffold'\n\n// Registration is lazy (called from the portal renderApp wrapper) so that\n// importing the @fayz-ai/portal barrel stays side-effect-free and tree-shakable.\nlet memberScaffoldRegistered = false\nexport function registerMemberScaffold(): void {\n if (memberScaffoldRegistered) return\n memberScaffoldRegistered = true\n registerScaffold('member', MemberScaffold, { source: 'sdk', label: 'Member' })\n}\n","// @fayz-ai/portal — the authenticated member/learner surface (counterpart of\n// @fayz-ai/shop). Importing this package registers the 'member' scaffold.\nexport type { MemberConfig, ResolvedMemberConfig } from './config'\nexport { MemberConfigProvider, useMemberConfig, resolveConfig } from './config'\n\nexport { createMemberApp, initMemberRuntime, MemberShell } from './createMemberApp'\nexport { defineMember, MemberScaffold } from './scaffold'\n\nexport { useMemberSession } from './session'\nexport type { MemberSessionState } from './session'\n\nexport {\n initMemberAuth,\n establishMemberSession,\n signUpMember,\n signOutMember,\n customerIdForEmail,\n resolveAuthAdapter,\n} from './auth'\n\nexport { useHashPath, matchPath, navigateTo, Link } from './router'\n\nexport { MyCoursesPage } from './pages/MyCoursesPage'\nexport { CoursePlayerPage } from './pages/CoursePlayerPage'\nexport { AccountPage } from './pages/AccountPage'\nexport { MemberHeader } from './components/MemberHeader'\n\n// ---------------------------------------------------------------------------\n// Front-door re-exports: member apps depend on @fayz-ai/portal alone.\n// Runtime (renderApp/defineApp) from @fayz-ai/core; the courses provider API\n// from @fayz-ai/courses, so plugins.generated.ts wires providers through the\n// portal front door. Mirrors the @fayz-ai/saas front door.\n// ---------------------------------------------------------------------------\n// renderApp is wrapped (not re-exported) so the 'member' scaffold registers on\n// first render instead of at import time — keeps the barrel side-effect-free.\nimport { renderApp as coreRenderApp } from '@fayz-ai/core'\nimport { registerMemberScaffold } from './scaffold'\n\nexport const renderApp: typeof coreRenderApp = (...args) => {\n registerMemberScaffold()\n return coreRenderApp(...args)\n}\n\nexport { defineApp } from '@fayz-ai/core'\nexport { registerMemberScaffold } from './scaffold'\nexport {\n setCoursesProvider,\n getCoursesProvider,\n getCoursesProviderOptional,\n MockCoursesProvider,\n createMockCoursesProvider,\n useMyCourses,\n useCourse,\n useCourseProgress,\n} from '@fayz-ai/courses'\nexport type { CoursesProvider, MockCoursesSeed } from '@fayz-ai/courses'\n"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/data/session.ts","../src/data/auth.ts","../src/app/router.ts","../src/components/MemberHeader.tsx","../src/views/MyCoursesPage.tsx","../src/views/CoursePlayerPage.tsx","../src/views/AccountPage.tsx","../src/app/createMemberApp.tsx","../src/app/scaffold.tsx","../src/index.ts"],"names":["React","adapter","useState","jsxs","jsx","slug","getCoursesProvider","coreRenderApp"],"mappings":"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","file":"index.js","sourcesContent":["import React from 'react'\n\n// ---------------------------------------------------------------------------\n// MemberConfig — the declarative surface for the learner portal. Pure data so\n// it round-trips through an AppManifest (defineMember ↔ MemberScaffold).\n// ---------------------------------------------------------------------------\n\nexport interface MemberConfig {\n name: string\n /** Logo image URL shown in the header (falls back to the name). */\n logoUrl?: string\n locale?: string\n /** Accent color (CSS color) for the player progress + active states. */\n accent?: string\n /** Auth adapter: 'mock' (default) | 'supabase' | a bring-your-own adapter. */\n auth?: { adapter?: 'mock' | 'supabase' | unknown }\n supabaseUrl?: string\n supabaseAnonKey?: string\n /** Grant the logged-in learner every published course (demo behaviour). When\n * false, the learner only sees the courses they were explicitly enrolled in. */\n autoEnroll?: boolean\n}\n\nexport interface ResolvedMemberConfig extends MemberConfig {\n locale: string\n}\n\nexport function resolveConfig(config: MemberConfig): ResolvedMemberConfig {\n return { ...config, locale: config.locale ?? 'pt-BR' }\n}\n\nconst MemberConfigContext = React.createContext<ResolvedMemberConfig | null>(null)\nexport const MemberConfigProvider = MemberConfigContext.Provider\n\nexport function useMemberConfig(): ResolvedMemberConfig {\n const ctx = React.useContext(MemberConfigContext)\n if (!ctx) throw new Error('useMemberConfig must be used inside a MemberConfigProvider')\n return ctx\n}\n","import { create } from 'zustand'\nimport { persist } from 'zustand/middleware'\n\n// Learner session — mirrors the auth identity for synchronous UI access. The\n// customerId is the key courses enrollments/progress are stored against.\n\nexport interface MemberSessionState {\n customerId: string | null\n email: string | null\n name: string | null\n setSession(s: { customerId: string; email: string; name?: string | null }): void\n signOut(): void\n}\n\nexport const useMemberSession = create<MemberSessionState>()(\n persist(\n (set) => ({\n customerId: null,\n email: null,\n name: null,\n setSession: ({ customerId, email, name }) => set({ customerId, email, name: name ?? null }),\n signOut: () => set({ customerId: null, email: null, name: null }),\n }),\n { name: 'fayz.portal.session.v1' },\n ),\n)\n","import type { AuthAdapter, AuthUser } from '@fayz-ai/core'\nimport { createMockAuthAdapter, createSupabaseAuthAdapter } from '@fayz-ai/auth'\nimport { getCoursesProvider } from '@fayz-ai/courses'\nimport { useMemberSession } from './session'\n\n// ---------------------------------------------------------------------------\n// Learner auth — the SAME AuthAdapter contract the admin + storefront use. On\n// top of the auth identity, the portal derives a stable customerId (the key\n// enrollments/progress hang off) and, in demo mode, grants the learner every\n// published course (so any login lands in a populated \"my courses\").\n// ---------------------------------------------------------------------------\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')\n}\n\n/** Deterministic learner id from an email, so the same login always maps to the\n * same enrollments/progress across reloads. */\nexport function customerIdForEmail(email: string): string {\n return `cust-${slug(email.trim().toLowerCase())}`\n}\n\nlet _adapter: AuthAdapter | null = null\nlet _autoEnroll = true\n\nexport function resolveAuthAdapter(\n configured: unknown,\n supabase?: { url?: string; anonKey?: string },\n): AuthAdapter {\n if (configured && configured !== 'mock' && configured !== 'supabase') return configured as AuthAdapter\n if (configured === 'supabase' && supabase?.url && supabase?.anonKey) {\n return createSupabaseAuthAdapter({ supabaseUrl: supabase.url, supabaseAnonKey: supabase.anonKey })\n }\n return createMockAuthAdapter()\n}\n\nexport function initMemberAuth(adapter: AuthAdapter, opts?: { autoEnroll?: boolean }): void {\n _adapter = adapter\n _autoEnroll = opts?.autoEnroll ?? true\n adapter\n .getSession()\n .then((existing) => {\n if (existing) void link(existing.user)\n })\n .catch(() => {})\n adapter.onAuthStateChange((user) => {\n if (!user) useMemberSession.getState().signOut()\n })\n}\n\nfunction getAdapter(): AuthAdapter {\n if (!_adapter) _adapter = createMockAuthAdapter()\n return _adapter\n}\n\n/** Enroll the learner in every published course — demo entitlement. Idempotent. */\nasync function grantAllPublished(customerId: string): Promise<void> {\n const provider = getCoursesProvider()\n const courses = await provider.listCourses({ status: 'published' })\n for (const c of courses) await provider.enroll(c.id, customerId)\n}\n\nasync function link(user: AuthUser, fullName?: string): Promise<{ customerId: string }> {\n const email = (user.email ?? '').trim().toLowerCase()\n const customerId = customerIdForEmail(email)\n if (_autoEnroll) await grantAllPublished(customerId)\n useMemberSession.getState().setSession({ customerId, email, name: fullName ?? user.fullName ?? null })\n return { customerId }\n}\n\nexport async function establishMemberSession(\n email: string,\n opts?: { password?: string; name?: string },\n): Promise<{ customerId: string }> {\n const adapter = getAdapter()\n const normalized = email.trim().toLowerCase()\n const session = opts?.password\n ? await adapter.signIn(normalized, opts.password)\n : await adapter.signUp(normalized, `member-${normalized}`, opts?.name ?? '')\n const user = session.user.email ? session.user : { ...session.user, email: normalized }\n return link(user, opts?.name)\n}\n\nexport async function signUpMember(email: string, password: string, name: string): Promise<{ customerId: string }> {\n const adapter = getAdapter()\n const normalized = email.trim().toLowerCase()\n const session = await adapter.signUp(normalized, password, name)\n const user = session.user.email ? session.user : { ...session.user, email: normalized }\n return link(user, name)\n}\n\nexport async function signOutMember(): Promise<void> {\n try {\n await getAdapter().signOut()\n } finally {\n useMemberSession.getState().signOut()\n }\n}\n","import React, { useEffect, useState } from 'react'\nimport { hashRouterAdapter } from '@fayz-ai/core'\n\n// Minimal hash router for the member portal — the same lightweight approach the\n// storefront uses (no react-router; avoids singleton hazards under source-alias).\n\nconst adapter = hashRouterAdapter()\n\nexport function navigateTo(to: string): void {\n adapter.navigate(to)\n}\n\nexport function useHashPath(): string {\n const [path, setPath] = useState(() => adapter.getCurrentPath() || '/')\n useEffect(() => {\n return adapter.onPathChange((p) => {\n setPath(p || '/')\n if (typeof window !== 'undefined') window.scrollTo(0, 0)\n })\n }, [])\n return path\n}\n\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const cleanPath = (path.split('?')[0] ?? '').replace(/\\/+$/, '') || '/'\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = cleanPath.split('/').filter(Boolean)\n if (patternParts.length !== pathParts.length) return null\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const pat = patternParts[i]!\n const part = pathParts[i]!\n if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)\n else if (pat !== part) return null\n }\n return params\n}\n\nexport interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {\n to: string\n}\n\nexport function Link({ to, children, ...rest }: LinkProps) {\n return React.createElement('a', { href: `#${to}`, ...rest }, children)\n}\n","import React from 'react'\nimport { useMemberConfig } from '../config'\nimport { useMemberSession } from '../data/session'\nimport { signOutMember } from '../data/auth'\nimport { Link, navigateTo } from '../app/router'\n\nexport function MemberHeader() {\n const config = useMemberConfig()\n const session = useMemberSession()\n const [menuOpen, setMenuOpen] = React.useState(false)\n\n return (\n <header className=\"sticky top-0 z-30 border-b border-border bg-card/95 backdrop-blur\">\n <div className=\"mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6\">\n <Link to=\"/\" className=\"flex items-center gap-2 font-bold text-foreground\" data-testid=\"member-logo\">\n {config.logoUrl ? (\n <img src={config.logoUrl} alt={config.name} className=\"h-7\" />\n ) : (\n <span>{config.name}</span>\n )}\n </Link>\n\n {session.customerId ? (\n <div className=\"relative\">\n <button\n data-testid=\"member-usermenu\"\n onClick={() => setMenuOpen((v) => !v)}\n className=\"flex items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted\"\n >\n <span className=\"flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground\">\n {(session.email ?? '?').charAt(0).toUpperCase()}\n </span>\n <span className=\"hidden sm:inline\">{session.name || session.email}</span>\n </button>\n {menuOpen && (\n <div className=\"absolute right-0 mt-2 w-44 overflow-hidden rounded-lg border border-border bg-card shadow-lg\">\n <button\n onClick={() => { setMenuOpen(false); navigateTo('/account') }}\n className=\"block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted\"\n >\n Minha conta\n </button>\n <button\n data-testid=\"member-header-signout\"\n onClick={() => { setMenuOpen(false); void signOutMember(); navigateTo('/') }}\n className=\"block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted\"\n >\n Sair\n </button>\n </div>\n )}\n </div>\n ) : null}\n </div>\n </header>\n )\n}\n","import React, { useState } from 'react'\nimport { useMyCourses } from '@fayz-ai/courses'\nimport { useMemberSession } from '../data/session'\nimport { establishMemberSession } from '../data/auth'\nimport { useMemberConfig } from '../config'\nimport { navigateTo } from '../app/router'\n\nfunction AuthForm() {\n const config = useMemberConfig()\n const [email, setEmail] = useState('')\n const [password, setPassword] = useState('')\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n async function submit(e: React.FormEvent) {\n e.preventDefault()\n setError(null)\n if (!email.trim() || !password) return\n setBusy(true)\n try {\n await establishMemberSession(email, { password })\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Não foi possível entrar.')\n } finally {\n setBusy(false)\n }\n }\n\n return (\n <div className=\"mx-auto max-w-sm rounded-xl border border-border bg-card p-7 shadow-sm\">\n <h2 className=\"text-xl font-semibold text-foreground\">{config.name}</h2>\n <p className=\"mt-1 text-sm text-muted-foreground\">Entre para acessar seus cursos.</p>\n <form className=\"mt-5 space-y-3\" onSubmit={submit}>\n <input\n data-testid=\"member-email\"\n type=\"email\"\n required\n placeholder=\"voce@exemplo.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm\"\n />\n <input\n data-testid=\"member-password\"\n type=\"password\"\n required\n placeholder=\"Senha\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm\"\n />\n {error && <p data-testid=\"member-auth-error\" className=\"text-sm text-destructive\">{error}</p>}\n <button\n type=\"submit\"\n data-testid=\"member-signin\"\n disabled={busy}\n className=\"w-full rounded-md bg-primary py-2.5 font-semibold text-primary-foreground disabled:opacity-60\"\n >\n {busy ? 'Entrando…' : 'Entrar'}\n </button>\n </form>\n </div>\n )\n}\n\nexport function MyCoursesPage() {\n const session = useMemberSession()\n const { courses, loading } = useMyCourses(session.customerId)\n\n if (!session.customerId) {\n return (\n <main className=\"mx-auto max-w-3xl px-4 py-12 sm:px-6\">\n <AuthForm />\n </main>\n )\n }\n\n return (\n <main className=\"mx-auto max-w-5xl px-4 py-8 sm:px-6\">\n <h1 className=\"mb-1 text-2xl font-bold tracking-tight text-foreground\">Meus cursos</h1>\n <p className=\"mb-6 text-sm text-muted-foreground\">{session.email}</p>\n\n {loading ? (\n <div className=\"py-16 text-center text-muted-foreground\">Carregando…</div>\n ) : courses.length === 0 ? (\n <div data-testid=\"member-empty\" className=\"rounded-xl border border-border bg-card py-16 text-center text-muted-foreground\">\n Você ainda não tem cursos.\n </div>\n ) : (\n <div data-testid=\"member-courses\" className=\"grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3\">\n {courses.map(({ course, progressPercent, completedLessons, totalLessons }) => (\n <button\n key={course.id}\n data-testid=\"member-course-card\"\n data-slug={course.slug}\n onClick={() => navigateTo(`/course/${course.slug}`)}\n className=\"group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md\"\n >\n <div className=\"aspect-video w-full overflow-hidden bg-muted\">\n {course.thumbnailUrl && <img src={course.thumbnailUrl} alt=\"\" className=\"h-full w-full object-cover\" />}\n </div>\n <div className=\"space-y-2 p-4\">\n <h3 className=\"font-semibold text-foreground line-clamp-2\">{course.title}</h3>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-muted\">\n <div className=\"h-full rounded-full bg-primary transition-all\" style={{ width: `${progressPercent}%` }} />\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {completedLessons}/{totalLessons} aulas · {progressPercent}%\n </p>\n </div>\n </button>\n ))}\n </div>\n )}\n </main>\n )\n}\n","import React from 'react'\nimport { useCourse, useCourseProgress, getCoursesProvider } from '@fayz-ai/courses'\nimport { useMemberSession } from '../data/session'\nimport { navigateTo } from '../app/router'\n\nexport function CoursePlayerPage({ slug, lessonId }: { slug: string; lessonId?: string }) {\n const session = useMemberSession()\n const { course, modules, lessons, loading } = useCourse(slug)\n const [enrollmentId, setEnrollmentId] = React.useState<string | null>(null)\n\n // Resolve this learner's enrollment for the course → drives progress.\n React.useEffect(() => {\n let active = true\n if (!course || !session.customerId) return\n ;(async () => {\n const provider = getCoursesProvider()\n const enrollments = await provider.listEnrollments(session.customerId!)\n let enrollment = enrollments.find((e) => e.courseId === course.id)\n if (!enrollment) enrollment = await provider.enroll(course.id, session.customerId!)\n if (active) setEnrollmentId(enrollment.id)\n })()\n return () => { active = false }\n }, [course, session.customerId])\n\n const { isCompleted, markLessonComplete } = useCourseProgress(enrollmentId)\n\n if (loading || !course) {\n return <main className=\"px-4 py-12 text-center text-muted-foreground\">Carregando…</main>\n }\n\n // Flatten lessons in true curriculum order (module order, then lesson order).\n // The provider sorts lessons by their per-module sortOrder, so a plain flat\n // list interleaves modules — walk modules to get the real sequence.\n const orderedLessons = modules.flatMap((m) => lessons.filter((l) => l.moduleId === m.id))\n\n const activeLesson = (lessonId && orderedLessons.find((l) => l.id === lessonId)) || orderedLessons[0]\n if (!activeLesson) {\n return <main className=\"px-4 py-12 text-center text-muted-foreground\">Curso sem aulas ainda.</main>\n }\n\n const idx = orderedLessons.findIndex((l) => l.id === activeLesson.id)\n const nextLesson = idx >= 0 && idx < orderedLessons.length - 1 ? orderedLessons[idx + 1] : null\n\n function open(lid: string) {\n navigateTo(`/course/${course!.slug}/lesson/${lid}`)\n }\n\n async function completeAndAdvance() {\n await markLessonComplete(activeLesson!.id, true)\n if (nextLesson) open(nextLesson.id)\n }\n\n return (\n <main className=\"mx-auto grid max-w-6xl grid-cols-1 gap-6 px-4 py-6 sm:px-6 lg:grid-cols-[1fr_320px]\">\n {/* Player + lesson body */}\n <div>\n <button onClick={() => navigateTo('/')} className=\"mb-3 text-sm text-muted-foreground hover:text-foreground\">\n ← Meus cursos\n </button>\n <div className=\"aspect-video w-full overflow-hidden rounded-xl bg-black\">\n <iframe\n key={activeLesson.id}\n data-testid=\"lesson-video\"\n src={activeLesson.videoUrl}\n title={activeLesson.title}\n className=\"h-full w-full\"\n allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n allowFullScreen\n />\n </div>\n <div className=\"mt-4 flex items-start justify-between gap-4\">\n <div>\n <p className=\"text-xs text-muted-foreground\">{course.title}</p>\n <h1 data-testid=\"lesson-title\" className=\"text-xl font-bold text-foreground\">{activeLesson.title}</h1>\n </div>\n <button\n data-testid=\"mark-complete\"\n onClick={completeAndAdvance}\n className={`shrink-0 rounded-md px-4 py-2 text-sm font-semibold ${\n isCompleted(activeLesson.id)\n ? 'border border-border bg-card text-muted-foreground'\n : 'bg-primary text-primary-foreground'\n }`}\n >\n {isCompleted(activeLesson.id) ? '✓ Concluída' : 'Marcar como concluída'}\n </button>\n </div>\n {activeLesson.description && (\n <p className=\"mt-3 text-sm text-muted-foreground\">{activeLesson.description}</p>\n )}\n </div>\n\n {/* Curriculum sidebar */}\n <aside data-testid=\"lesson-sidebar\" className=\"rounded-xl border border-border bg-card p-3\">\n {modules.map((mod) => (\n <div key={mod.id} className=\"mb-3\">\n <p className=\"px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground\">{mod.title}</p>\n <ul className=\"space-y-0.5\">\n {lessons.filter((l) => l.moduleId === mod.id).map((lesson) => {\n const done = isCompleted(lesson.id)\n const current = lesson.id === activeLesson.id\n return (\n <li key={lesson.id}>\n <button\n data-testid=\"sidebar-lesson\"\n data-completed={done ? 'true' : 'false'}\n onClick={() => open(lesson.id)}\n className={`flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm ${\n current ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:bg-muted/60'\n }`}\n >\n <span className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full border text-[10px] ${\n done ? 'border-primary bg-primary text-primary-foreground' : 'border-border'\n }`}>{done ? '✓' : ''}</span>\n <span className=\"line-clamp-2\">{lesson.title}</span>\n </button>\n </li>\n )\n })}\n </ul>\n </div>\n ))}\n </aside>\n </main>\n )\n}\n","import React from 'react'\nimport { useMemberSession } from '../data/session'\nimport { signOutMember } from '../data/auth'\nimport { navigateTo } from '../app/router'\n\nexport function AccountPage() {\n const session = useMemberSession()\n\n if (!session.customerId) {\n navigateTo('/')\n return null\n }\n\n return (\n <main className=\"mx-auto max-w-xl px-4 py-10 sm:px-6\">\n <h1 className=\"mb-6 text-2xl font-bold text-foreground\">Minha conta</h1>\n <div className=\"space-y-4 rounded-xl border border-border bg-card p-6\">\n <Row label=\"Nome\" value={session.name || '—'} />\n <Row label=\"E-mail\" value={session.email || '—'} />\n <button\n data-testid=\"account-signout\"\n onClick={() => { void signOutMember(); navigateTo('/') }}\n className=\"mt-2 rounded-md border border-border bg-card px-4 py-2 text-sm font-medium text-foreground hover:bg-muted\"\n >\n Sair\n </button>\n </div>\n </main>\n )\n}\n\nfunction Row({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex justify-between border-b border-border pb-3 last:border-0 last:pb-0\">\n <span className=\"text-sm text-muted-foreground\">{label}</span>\n <span className=\"text-sm font-medium text-foreground\">{value}</span>\n </div>\n )\n}\n","import React from 'react'\nimport { MemberConfigProvider, resolveConfig, useMemberConfig } from '../config'\nimport type { MemberConfig } from '../config'\nimport { initMemberAuth, resolveAuthAdapter } from '../data/auth'\nimport { useHashPath, matchPath } from './router'\nimport { MemberHeader } from '../components/MemberHeader'\nimport { MyCoursesPage } from '../views/MyCoursesPage'\nimport { CoursePlayerPage } from '../views/CoursePlayerPage'\nimport { AccountPage } from '../views/AccountPage'\n\nfunction RouteSwitch() {\n const path = useHashPath()\n\n const lesson = matchPath('/course/:slug/lesson/:id', path)\n if (lesson?.slug) return <CoursePlayerPage slug={lesson.slug} lessonId={lesson.id} />\n\n const course = matchPath('/course/:slug', path)\n if (course?.slug) return <CoursePlayerPage slug={course.slug} />\n\n if (matchPath('/account', path)) return <AccountPage />\n\n return <MyCoursesPage />\n}\n\n/** Side-effect runtime init shared by the factory and the manifest scaffold:\n * wires learner auth (+ optional Supabase client). Idempotent. */\nexport function initMemberRuntime(config: MemberConfig): void {\n // The courses data provider is owned by the host app (mock or Supabase). The\n // portal only wires learner auth here; a Supabase-backed app sets the global\n // client + provider before mounting.\n initMemberAuth(\n resolveAuthAdapter(config.auth?.adapter, { url: config.supabaseUrl, anonKey: config.supabaseAnonKey }),\n { autoEnroll: config.autoEnroll ?? true },\n )\n}\n\n/** Inner portal UI — reads everything from config context, so it is shared by\n * createMemberApp and the manifest-driven MemberScaffold. */\nexport function MemberShell() {\n const config = useMemberConfig()\n return (\n <div className=\"min-h-screen bg-background text-foreground\" data-portal={config.name}>\n {config.accent && (\n <style>{`:root{--primary:${config.accent};}`}</style>\n )}\n <MemberHeader />\n <RouteSwitch />\n </div>\n )\n}\n\n/** Declarative member-portal factory — sugar over the manifest path (equivalent\n * to renderApp(defineMember(config))). */\nexport function createMemberApp(config: MemberConfig): React.ComponentType {\n const resolved = resolveConfig(config)\n initMemberRuntime(config)\n function MemberApp() {\n return (\n <MemberConfigProvider value={resolved}>\n <MemberShell />\n </MemberConfigProvider>\n )\n }\n MemberApp.displayName = 'MemberApp'\n return MemberApp\n}\n","import React from 'react'\nimport { registerScaffold, defineApp } from '@fayz-ai/core'\nimport type { AppManifest } from '@fayz-ai/core'\nimport { MemberConfigProvider, resolveConfig } from '../config'\nimport type { MemberConfig } from '../config'\nimport { initMemberRuntime, MemberShell } from './createMemberApp'\n\n// ---------------------------------------------------------------------------\n// Member scaffold — renders the learner portal from a pure-data AppManifest.\n// Mirrors the storefront scaffold's dual path: defineMember (config → manifest)\n// and MemberScaffold (manifest → render). This is the SDK's third surface type\n// (authenticated member area), alongside 'admin' and 'storefront'.\n// ---------------------------------------------------------------------------\n\nfunction slug(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'app'\n}\n\nexport function defineMember(config: MemberConfig): AppManifest {\n return defineApp({\n id: slug(config.name),\n name: config.name,\n backend: config.supabaseUrl ? { provider: 'supabase', url: config.supabaseUrl } : { provider: 'mock' },\n locale: { default: config.locale ?? 'pt-BR', supported: [config.locale ?? 'pt-BR'] },\n surfaces: {\n member: {\n scaffold: 'member',\n options: {\n logoUrl: config.logoUrl,\n accent: config.accent,\n autoEnroll: config.autoEnroll ?? true,\n },\n },\n },\n })\n}\n\nfunction manifestToMemberConfig(manifest: AppManifest, surfaceName: string): MemberConfig {\n const o = (manifest.surfaces[surfaceName]?.options ?? {}) as Record<string, unknown>\n return {\n name: manifest.name,\n locale: (manifest.locale as { default?: string } | undefined)?.default,\n logoUrl: o.logoUrl as string | undefined,\n accent: o.accent as string | undefined,\n autoEnroll: (o.autoEnroll as boolean | undefined) ?? true,\n supabaseUrl: manifest.backend?.provider === 'supabase' ? manifest.backend.url : undefined,\n }\n}\n\nexport function MemberScaffold({ manifest, surface }: { manifest: AppManifest; surface: string }) {\n const config = React.useMemo(() => manifestToMemberConfig(manifest, surface), [manifest, surface])\n const resolved = React.useMemo(() => resolveConfig(config), [config])\n const inited = React.useRef(false)\n if (!inited.current) {\n initMemberRuntime(config)\n inited.current = true\n }\n return (\n <MemberConfigProvider value={resolved}>\n <MemberShell />\n </MemberConfigProvider>\n )\n}\nMemberScaffold.displayName = 'MemberScaffold'\n\n// Registration is lazy (called from the portal renderApp wrapper) so that\n// importing the @fayz-ai/portal barrel stays side-effect-free and tree-shakable.\nlet memberScaffoldRegistered = false\nexport function registerMemberScaffold(): void {\n if (memberScaffoldRegistered) return\n memberScaffoldRegistered = true\n registerScaffold('member', MemberScaffold, { source: 'sdk', label: 'Member' })\n}\n","// @fayz-ai/portal — the authenticated member/learner surface (counterpart of\n// @fayz-ai/shop). Importing this package registers the 'member' scaffold.\nexport type { MemberConfig, ResolvedMemberConfig } from './config'\nexport { MemberConfigProvider, useMemberConfig, resolveConfig } from './config'\n\nexport { createMemberApp, initMemberRuntime, MemberShell } from './app/createMemberApp'\nexport { defineMember, MemberScaffold } from './app/scaffold'\n\nexport { useMemberSession } from './data/session'\nexport type { MemberSessionState } from './data/session'\n\nexport {\n initMemberAuth,\n establishMemberSession,\n signUpMember,\n signOutMember,\n customerIdForEmail,\n resolveAuthAdapter,\n} from './data/auth'\n\nexport { useHashPath, matchPath, navigateTo, Link } from './app/router'\n\nexport { MyCoursesPage } from './views/MyCoursesPage'\nexport { CoursePlayerPage } from './views/CoursePlayerPage'\nexport { AccountPage } from './views/AccountPage'\nexport { MemberHeader } from './components/MemberHeader'\n\n// ---------------------------------------------------------------------------\n// Front-door re-exports: member apps depend on @fayz-ai/portal alone.\n// Runtime (renderApp/defineApp) from @fayz-ai/core; the courses provider API\n// from @fayz-ai/courses, so plugins.generated.ts wires providers through the\n// portal front door. Mirrors the @fayz-ai/admin front door.\n// ---------------------------------------------------------------------------\n// renderApp is wrapped (not re-exported) so the 'member' scaffold registers on\n// first render instead of at import time — keeps the barrel side-effect-free.\nimport { renderApp as coreRenderApp } from '@fayz-ai/core'\nimport { registerMemberScaffold } from './app/scaffold'\n\nexport const renderApp: typeof coreRenderApp = (...args) => {\n registerMemberScaffold()\n return coreRenderApp(...args)\n}\n\nexport { defineApp } from '@fayz-ai/core'\nexport { registerMemberScaffold } from './app/scaffold'\nexport {\n setCoursesProvider,\n getCoursesProvider,\n getCoursesProviderOptional,\n MockCoursesProvider,\n createMockCoursesProvider,\n useMyCourses,\n useCourse,\n useCourseProgress,\n} from '@fayz-ai/courses'\nexport type { CoursesProvider, MockCoursesSeed } from '@fayz-ai/courses'\n"]}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountPage.d.ts","sourceRoot":"","sources":["../../src/views/AccountPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,wBAAgB,WAAW,6BAwB1B"}
@@ -1 +1 @@
1
- {"version":3,"file":"CoursePlayerPage.d.ts","sourceRoot":"","sources":["../../src/pages/CoursePlayerPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,qBAwHvF"}
1
+ {"version":3,"file":"CoursePlayerPage.d.ts","sourceRoot":"","sources":["../../src/views/CoursePlayerPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,wBAAgB,gBAAgB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,qBAwHvF"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MyCoursesPage.d.ts","sourceRoot":"","sources":["../../src/views/MyCoursesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAiEvC,wBAAgB,aAAa,sBAmD5B"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.9.0",
6
+ "version": "0.11.0",
7
7
  "description": "Fayz SDK member/learner portal scaffold — authenticated 'my courses' + player surface",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -12,19 +12,17 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "exports": {
14
14
  ".": {
15
- "source": "./src/index.ts",
16
15
  "types": "./dist/index.d.ts",
17
16
  "import": "./dist/index.js"
18
17
  }
19
18
  },
20
19
  "files": [
21
- "dist",
22
- "src"
20
+ "dist"
23
21
  ],
24
22
  "dependencies": {
25
- "@fayz-ai/core": "^0.9.0",
26
- "@fayz-ai/auth": "^0.9.0",
27
- "@fayz-ai/courses": "^0.9.0"
23
+ "@fayz-ai/core": "^0.11.0",
24
+ "@fayz-ai/auth": "^0.11.0",
25
+ "@fayz-ai/courses": "^0.11.0"
28
26
  },
29
27
  "peerDependencies": {
30
28
  "react": "^18.0.0 || ^19.0.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAY,MAAM,eAAe,CAAA;AAgB1D;gDACgD;AAChD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAKD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,QAAQ,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5C,WAAW,CAMb;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAY1F;AAsBD,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAQjC;AAED,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAMjH;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAMnD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"createMemberApp.d.ts","sourceRoot":"","sources":["../src/createMemberApp.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAsB5C;mEACmE;AACnE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAQ5D;AAED;8DAC8D;AAC9D,wBAAgB,WAAW,sBAW1B;AAED;2CAC2C;AAC3C,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,KAAK,CAAC,aAAa,CAYzE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"AccountPage.d.ts","sourceRoot":"","sources":["../../src/pages/AccountPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAKzB,wBAAgB,WAAW,6BAwB1B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MyCoursesPage.d.ts","sourceRoot":"","sources":["../../src/pages/MyCoursesPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAiEvC,wBAAgB,aAAa,sBAmD5B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAA;AAQlD,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAE3C;AAED,wBAAgB,WAAW,IAAI,MAAM,CASpC;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAatF;AAED,MAAM,WAAW,SAAU,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAC9E,EAAE,EAAE,MAAM,CAAA;CACX;AAED,wBAAgB,IAAI,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAExD"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAc5C,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAiB9D;AAcD,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,qBAa/F;yBAbe,cAAc;;;AAmB9B,wBAAgB,sBAAsB,IAAI,IAAI,CAI7C"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,UAAU,CAAC,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAChF,OAAO,IAAI,IAAI,CAAA;CAChB;AAED,eAAO,MAAM,gBAAgB;;;;;;;;;;EAW5B,CAAA"}
package/src/auth.ts DELETED
@@ -1,98 +0,0 @@
1
- import type { AuthAdapter, AuthUser } from '@fayz-ai/core'
2
- import { createMockAuthAdapter, createSupabaseAuthAdapter } from '@fayz-ai/auth'
3
- import { getCoursesProvider } from '@fayz-ai/courses'
4
- import { useMemberSession } from './session'
5
-
6
- // ---------------------------------------------------------------------------
7
- // Learner auth — the SAME AuthAdapter contract the admin + storefront use. On
8
- // top of the auth identity, the portal derives a stable customerId (the key
9
- // enrollments/progress hang off) and, in demo mode, grants the learner every
10
- // published course (so any login lands in a populated "my courses").
11
- // ---------------------------------------------------------------------------
12
-
13
- function slug(s: string): string {
14
- return s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
15
- }
16
-
17
- /** Deterministic learner id from an email, so the same login always maps to the
18
- * same enrollments/progress across reloads. */
19
- export function customerIdForEmail(email: string): string {
20
- return `cust-${slug(email.trim().toLowerCase())}`
21
- }
22
-
23
- let _adapter: AuthAdapter | null = null
24
- let _autoEnroll = true
25
-
26
- export function resolveAuthAdapter(
27
- configured: unknown,
28
- supabase?: { url?: string; anonKey?: string },
29
- ): AuthAdapter {
30
- if (configured && configured !== 'mock' && configured !== 'supabase') return configured as AuthAdapter
31
- if (configured === 'supabase' && supabase?.url && supabase?.anonKey) {
32
- return createSupabaseAuthAdapter({ supabaseUrl: supabase.url, supabaseAnonKey: supabase.anonKey })
33
- }
34
- return createMockAuthAdapter()
35
- }
36
-
37
- export function initMemberAuth(adapter: AuthAdapter, opts?: { autoEnroll?: boolean }): void {
38
- _adapter = adapter
39
- _autoEnroll = opts?.autoEnroll ?? true
40
- adapter
41
- .getSession()
42
- .then((existing) => {
43
- if (existing) void link(existing.user)
44
- })
45
- .catch(() => {})
46
- adapter.onAuthStateChange((user) => {
47
- if (!user) useMemberSession.getState().signOut()
48
- })
49
- }
50
-
51
- function getAdapter(): AuthAdapter {
52
- if (!_adapter) _adapter = createMockAuthAdapter()
53
- return _adapter
54
- }
55
-
56
- /** Enroll the learner in every published course — demo entitlement. Idempotent. */
57
- async function grantAllPublished(customerId: string): Promise<void> {
58
- const provider = getCoursesProvider()
59
- const courses = await provider.listCourses({ status: 'published' })
60
- for (const c of courses) await provider.enroll(c.id, customerId)
61
- }
62
-
63
- async function link(user: AuthUser, fullName?: string): Promise<{ customerId: string }> {
64
- const email = (user.email ?? '').trim().toLowerCase()
65
- const customerId = customerIdForEmail(email)
66
- if (_autoEnroll) await grantAllPublished(customerId)
67
- useMemberSession.getState().setSession({ customerId, email, name: fullName ?? user.fullName ?? null })
68
- return { customerId }
69
- }
70
-
71
- export async function establishMemberSession(
72
- email: string,
73
- opts?: { password?: string; name?: string },
74
- ): Promise<{ customerId: string }> {
75
- const adapter = getAdapter()
76
- const normalized = email.trim().toLowerCase()
77
- const session = opts?.password
78
- ? await adapter.signIn(normalized, opts.password)
79
- : await adapter.signUp(normalized, `member-${normalized}`, opts?.name ?? '')
80
- const user = session.user.email ? session.user : { ...session.user, email: normalized }
81
- return link(user, opts?.name)
82
- }
83
-
84
- export async function signUpMember(email: string, password: string, name: string): Promise<{ customerId: string }> {
85
- const adapter = getAdapter()
86
- const normalized = email.trim().toLowerCase()
87
- const session = await adapter.signUp(normalized, password, name)
88
- const user = session.user.email ? session.user : { ...session.user, email: normalized }
89
- return link(user, name)
90
- }
91
-
92
- export async function signOutMember(): Promise<void> {
93
- try {
94
- await getAdapter().signOut()
95
- } finally {
96
- useMemberSession.getState().signOut()
97
- }
98
- }
@@ -1,57 +0,0 @@
1
- import React from 'react'
2
- import { useMemberConfig } from '../config'
3
- import { useMemberSession } from '../session'
4
- import { signOutMember } from '../auth'
5
- import { Link, navigateTo } from '../router'
6
-
7
- export function MemberHeader() {
8
- const config = useMemberConfig()
9
- const session = useMemberSession()
10
- const [menuOpen, setMenuOpen] = React.useState(false)
11
-
12
- return (
13
- <header className="sticky top-0 z-30 border-b border-border bg-card/95 backdrop-blur">
14
- <div className="mx-auto flex h-14 max-w-6xl items-center justify-between px-4 sm:px-6">
15
- <Link to="/" className="flex items-center gap-2 font-bold text-foreground" data-testid="member-logo">
16
- {config.logoUrl ? (
17
- <img src={config.logoUrl} alt={config.name} className="h-7" />
18
- ) : (
19
- <span>{config.name}</span>
20
- )}
21
- </Link>
22
-
23
- {session.customerId ? (
24
- <div className="relative">
25
- <button
26
- data-testid="member-usermenu"
27
- onClick={() => setMenuOpen((v) => !v)}
28
- className="flex items-center gap-2 rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-foreground hover:bg-muted"
29
- >
30
- <span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-xs text-primary-foreground">
31
- {(session.email ?? '?').charAt(0).toUpperCase()}
32
- </span>
33
- <span className="hidden sm:inline">{session.name || session.email}</span>
34
- </button>
35
- {menuOpen && (
36
- <div className="absolute right-0 mt-2 w-44 overflow-hidden rounded-lg border border-border bg-card shadow-lg">
37
- <button
38
- onClick={() => { setMenuOpen(false); navigateTo('/account') }}
39
- className="block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted"
40
- >
41
- Minha conta
42
- </button>
43
- <button
44
- data-testid="member-header-signout"
45
- onClick={() => { setMenuOpen(false); void signOutMember(); navigateTo('/') }}
46
- className="block w-full px-4 py-2.5 text-left text-sm text-foreground hover:bg-muted"
47
- >
48
- Sair
49
- </button>
50
- </div>
51
- )}
52
- </div>
53
- ) : null}
54
- </div>
55
- </header>
56
- )
57
- }
package/src/config.ts DELETED
@@ -1,39 +0,0 @@
1
- import React from 'react'
2
-
3
- // ---------------------------------------------------------------------------
4
- // MemberConfig — the declarative surface for the learner portal. Pure data so
5
- // it round-trips through an AppManifest (defineMember ↔ MemberScaffold).
6
- // ---------------------------------------------------------------------------
7
-
8
- export interface MemberConfig {
9
- name: string
10
- /** Logo image URL shown in the header (falls back to the name). */
11
- logoUrl?: string
12
- locale?: string
13
- /** Accent color (CSS color) for the player progress + active states. */
14
- accent?: string
15
- /** Auth adapter: 'mock' (default) | 'supabase' | a bring-your-own adapter. */
16
- auth?: { adapter?: 'mock' | 'supabase' | unknown }
17
- supabaseUrl?: string
18
- supabaseAnonKey?: string
19
- /** Grant the logged-in learner every published course (demo behaviour). When
20
- * false, the learner only sees the courses they were explicitly enrolled in. */
21
- autoEnroll?: boolean
22
- }
23
-
24
- export interface ResolvedMemberConfig extends MemberConfig {
25
- locale: string
26
- }
27
-
28
- export function resolveConfig(config: MemberConfig): ResolvedMemberConfig {
29
- return { ...config, locale: config.locale ?? 'pt-BR' }
30
- }
31
-
32
- const MemberConfigContext = React.createContext<ResolvedMemberConfig | null>(null)
33
- export const MemberConfigProvider = MemberConfigContext.Provider
34
-
35
- export function useMemberConfig(): ResolvedMemberConfig {
36
- const ctx = React.useContext(MemberConfigContext)
37
- if (!ctx) throw new Error('useMemberConfig must be used inside a MemberConfigProvider')
38
- return ctx
39
- }
@@ -1,66 +0,0 @@
1
- import React from 'react'
2
- import { MemberConfigProvider, resolveConfig, useMemberConfig } from './config'
3
- import type { MemberConfig } from './config'
4
- import { initMemberAuth, resolveAuthAdapter } from './auth'
5
- import { useHashPath, matchPath } from './router'
6
- import { MemberHeader } from './components/MemberHeader'
7
- import { MyCoursesPage } from './pages/MyCoursesPage'
8
- import { CoursePlayerPage } from './pages/CoursePlayerPage'
9
- import { AccountPage } from './pages/AccountPage'
10
-
11
- function RouteSwitch() {
12
- const path = useHashPath()
13
-
14
- const lesson = matchPath('/course/:slug/lesson/:id', path)
15
- if (lesson?.slug) return <CoursePlayerPage slug={lesson.slug} lessonId={lesson.id} />
16
-
17
- const course = matchPath('/course/:slug', path)
18
- if (course?.slug) return <CoursePlayerPage slug={course.slug} />
19
-
20
- if (matchPath('/account', path)) return <AccountPage />
21
-
22
- return <MyCoursesPage />
23
- }
24
-
25
- /** Side-effect runtime init shared by the factory and the manifest scaffold:
26
- * wires learner auth (+ optional Supabase client). Idempotent. */
27
- export function initMemberRuntime(config: MemberConfig): void {
28
- // The courses data provider is owned by the host app (mock or Supabase). The
29
- // portal only wires learner auth here; a Supabase-backed app sets the global
30
- // client + provider before mounting.
31
- initMemberAuth(
32
- resolveAuthAdapter(config.auth?.adapter, { url: config.supabaseUrl, anonKey: config.supabaseAnonKey }),
33
- { autoEnroll: config.autoEnroll ?? true },
34
- )
35
- }
36
-
37
- /** Inner portal UI — reads everything from config context, so it is shared by
38
- * createMemberApp and the manifest-driven MemberScaffold. */
39
- export function MemberShell() {
40
- const config = useMemberConfig()
41
- return (
42
- <div className="min-h-screen bg-background text-foreground" data-portal={config.name}>
43
- {config.accent && (
44
- <style>{`:root{--primary:${config.accent};}`}</style>
45
- )}
46
- <MemberHeader />
47
- <RouteSwitch />
48
- </div>
49
- )
50
- }
51
-
52
- /** Declarative member-portal factory — sugar over the manifest path (equivalent
53
- * to renderApp(defineMember(config))). */
54
- export function createMemberApp(config: MemberConfig): React.ComponentType {
55
- const resolved = resolveConfig(config)
56
- initMemberRuntime(config)
57
- function MemberApp() {
58
- return (
59
- <MemberConfigProvider value={resolved}>
60
- <MemberShell />
61
- </MemberConfigProvider>
62
- )
63
- }
64
- MemberApp.displayName = 'MemberApp'
65
- return MemberApp
66
- }
package/src/index.ts DELETED
@@ -1,56 +0,0 @@
1
- // @fayz-ai/portal — the authenticated member/learner surface (counterpart of
2
- // @fayz-ai/shop). Importing this package registers the 'member' scaffold.
3
- export type { MemberConfig, ResolvedMemberConfig } from './config'
4
- export { MemberConfigProvider, useMemberConfig, resolveConfig } from './config'
5
-
6
- export { createMemberApp, initMemberRuntime, MemberShell } from './createMemberApp'
7
- export { defineMember, MemberScaffold } from './scaffold'
8
-
9
- export { useMemberSession } from './session'
10
- export type { MemberSessionState } from './session'
11
-
12
- export {
13
- initMemberAuth,
14
- establishMemberSession,
15
- signUpMember,
16
- signOutMember,
17
- customerIdForEmail,
18
- resolveAuthAdapter,
19
- } from './auth'
20
-
21
- export { useHashPath, matchPath, navigateTo, Link } from './router'
22
-
23
- export { MyCoursesPage } from './pages/MyCoursesPage'
24
- export { CoursePlayerPage } from './pages/CoursePlayerPage'
25
- export { AccountPage } from './pages/AccountPage'
26
- export { MemberHeader } from './components/MemberHeader'
27
-
28
- // ---------------------------------------------------------------------------
29
- // Front-door re-exports: member apps depend on @fayz-ai/portal alone.
30
- // Runtime (renderApp/defineApp) from @fayz-ai/core; the courses provider API
31
- // from @fayz-ai/courses, so plugins.generated.ts wires providers through the
32
- // portal front door. Mirrors the @fayz-ai/saas front door.
33
- // ---------------------------------------------------------------------------
34
- // renderApp is wrapped (not re-exported) so the 'member' scaffold registers on
35
- // first render instead of at import time — keeps the barrel side-effect-free.
36
- import { renderApp as coreRenderApp } from '@fayz-ai/core'
37
- import { registerMemberScaffold } from './scaffold'
38
-
39
- export const renderApp: typeof coreRenderApp = (...args) => {
40
- registerMemberScaffold()
41
- return coreRenderApp(...args)
42
- }
43
-
44
- export { defineApp } from '@fayz-ai/core'
45
- export { registerMemberScaffold } from './scaffold'
46
- export {
47
- setCoursesProvider,
48
- getCoursesProvider,
49
- getCoursesProviderOptional,
50
- MockCoursesProvider,
51
- createMockCoursesProvider,
52
- useMyCourses,
53
- useCourse,
54
- useCourseProgress,
55
- } from '@fayz-ai/courses'
56
- export type { CoursesProvider, MockCoursesSeed } from '@fayz-ai/courses'
@@ -1,39 +0,0 @@
1
- import React from 'react'
2
- import { useMemberSession } from '../session'
3
- import { signOutMember } from '../auth'
4
- import { navigateTo } from '../router'
5
-
6
- export function AccountPage() {
7
- const session = useMemberSession()
8
-
9
- if (!session.customerId) {
10
- navigateTo('/')
11
- return null
12
- }
13
-
14
- return (
15
- <main className="mx-auto max-w-xl px-4 py-10 sm:px-6">
16
- <h1 className="mb-6 text-2xl font-bold text-foreground">Minha conta</h1>
17
- <div className="space-y-4 rounded-xl border border-border bg-card p-6">
18
- <Row label="Nome" value={session.name || '—'} />
19
- <Row label="E-mail" value={session.email || '—'} />
20
- <button
21
- data-testid="account-signout"
22
- onClick={() => { void signOutMember(); navigateTo('/') }}
23
- className="mt-2 rounded-md border border-border bg-card px-4 py-2 text-sm font-medium text-foreground hover:bg-muted"
24
- >
25
- Sair
26
- </button>
27
- </div>
28
- </main>
29
- )
30
- }
31
-
32
- function Row({ label, value }: { label: string; value: string }) {
33
- return (
34
- <div className="flex justify-between border-b border-border pb-3 last:border-0 last:pb-0">
35
- <span className="text-sm text-muted-foreground">{label}</span>
36
- <span className="text-sm font-medium text-foreground">{value}</span>
37
- </div>
38
- )
39
- }
@@ -1,126 +0,0 @@
1
- import React from 'react'
2
- import { useCourse, useCourseProgress, getCoursesProvider } from '@fayz-ai/courses'
3
- import { useMemberSession } from '../session'
4
- import { navigateTo } from '../router'
5
-
6
- export function CoursePlayerPage({ slug, lessonId }: { slug: string; lessonId?: string }) {
7
- const session = useMemberSession()
8
- const { course, modules, lessons, loading } = useCourse(slug)
9
- const [enrollmentId, setEnrollmentId] = React.useState<string | null>(null)
10
-
11
- // Resolve this learner's enrollment for the course → drives progress.
12
- React.useEffect(() => {
13
- let active = true
14
- if (!course || !session.customerId) return
15
- ;(async () => {
16
- const provider = getCoursesProvider()
17
- const enrollments = await provider.listEnrollments(session.customerId!)
18
- let enrollment = enrollments.find((e) => e.courseId === course.id)
19
- if (!enrollment) enrollment = await provider.enroll(course.id, session.customerId!)
20
- if (active) setEnrollmentId(enrollment.id)
21
- })()
22
- return () => { active = false }
23
- }, [course, session.customerId])
24
-
25
- const { isCompleted, markLessonComplete } = useCourseProgress(enrollmentId)
26
-
27
- if (loading || !course) {
28
- return <main className="px-4 py-12 text-center text-muted-foreground">Carregando…</main>
29
- }
30
-
31
- // Flatten lessons in true curriculum order (module order, then lesson order).
32
- // The provider sorts lessons by their per-module sortOrder, so a plain flat
33
- // list interleaves modules — walk modules to get the real sequence.
34
- const orderedLessons = modules.flatMap((m) => lessons.filter((l) => l.moduleId === m.id))
35
-
36
- const activeLesson = (lessonId && orderedLessons.find((l) => l.id === lessonId)) || orderedLessons[0]
37
- if (!activeLesson) {
38
- return <main className="px-4 py-12 text-center text-muted-foreground">Curso sem aulas ainda.</main>
39
- }
40
-
41
- const idx = orderedLessons.findIndex((l) => l.id === activeLesson.id)
42
- const nextLesson = idx >= 0 && idx < orderedLessons.length - 1 ? orderedLessons[idx + 1] : null
43
-
44
- function open(lid: string) {
45
- navigateTo(`/course/${course!.slug}/lesson/${lid}`)
46
- }
47
-
48
- async function completeAndAdvance() {
49
- await markLessonComplete(activeLesson!.id, true)
50
- if (nextLesson) open(nextLesson.id)
51
- }
52
-
53
- return (
54
- <main className="mx-auto grid max-w-6xl grid-cols-1 gap-6 px-4 py-6 sm:px-6 lg:grid-cols-[1fr_320px]">
55
- {/* Player + lesson body */}
56
- <div>
57
- <button onClick={() => navigateTo('/')} className="mb-3 text-sm text-muted-foreground hover:text-foreground">
58
- ← Meus cursos
59
- </button>
60
- <div className="aspect-video w-full overflow-hidden rounded-xl bg-black">
61
- <iframe
62
- key={activeLesson.id}
63
- data-testid="lesson-video"
64
- src={activeLesson.videoUrl}
65
- title={activeLesson.title}
66
- className="h-full w-full"
67
- allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
68
- allowFullScreen
69
- />
70
- </div>
71
- <div className="mt-4 flex items-start justify-between gap-4">
72
- <div>
73
- <p className="text-xs text-muted-foreground">{course.title}</p>
74
- <h1 data-testid="lesson-title" className="text-xl font-bold text-foreground">{activeLesson.title}</h1>
75
- </div>
76
- <button
77
- data-testid="mark-complete"
78
- onClick={completeAndAdvance}
79
- className={`shrink-0 rounded-md px-4 py-2 text-sm font-semibold ${
80
- isCompleted(activeLesson.id)
81
- ? 'border border-border bg-card text-muted-foreground'
82
- : 'bg-primary text-primary-foreground'
83
- }`}
84
- >
85
- {isCompleted(activeLesson.id) ? '✓ Concluída' : 'Marcar como concluída'}
86
- </button>
87
- </div>
88
- {activeLesson.description && (
89
- <p className="mt-3 text-sm text-muted-foreground">{activeLesson.description}</p>
90
- )}
91
- </div>
92
-
93
- {/* Curriculum sidebar */}
94
- <aside data-testid="lesson-sidebar" className="rounded-xl border border-border bg-card p-3">
95
- {modules.map((mod) => (
96
- <div key={mod.id} className="mb-3">
97
- <p className="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-muted-foreground">{mod.title}</p>
98
- <ul className="space-y-0.5">
99
- {lessons.filter((l) => l.moduleId === mod.id).map((lesson) => {
100
- const done = isCompleted(lesson.id)
101
- const current = lesson.id === activeLesson.id
102
- return (
103
- <li key={lesson.id}>
104
- <button
105
- data-testid="sidebar-lesson"
106
- data-completed={done ? 'true' : 'false'}
107
- onClick={() => open(lesson.id)}
108
- className={`flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm ${
109
- current ? 'bg-muted font-medium text-foreground' : 'text-muted-foreground hover:bg-muted/60'
110
- }`}
111
- >
112
- <span className={`flex h-4 w-4 shrink-0 items-center justify-center rounded-full border text-[10px] ${
113
- done ? 'border-primary bg-primary text-primary-foreground' : 'border-border'
114
- }`}>{done ? '✓' : ''}</span>
115
- <span className="line-clamp-2">{lesson.title}</span>
116
- </button>
117
- </li>
118
- )
119
- })}
120
- </ul>
121
- </div>
122
- ))}
123
- </aside>
124
- </main>
125
- )
126
- }
@@ -1,117 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { useMyCourses } from '@fayz-ai/courses'
3
- import { useMemberSession } from '../session'
4
- import { establishMemberSession } from '../auth'
5
- import { useMemberConfig } from '../config'
6
- import { navigateTo } from '../router'
7
-
8
- function AuthForm() {
9
- const config = useMemberConfig()
10
- const [email, setEmail] = useState('')
11
- const [password, setPassword] = useState('')
12
- const [busy, setBusy] = useState(false)
13
- const [error, setError] = useState<string | null>(null)
14
-
15
- async function submit(e: React.FormEvent) {
16
- e.preventDefault()
17
- setError(null)
18
- if (!email.trim() || !password) return
19
- setBusy(true)
20
- try {
21
- await establishMemberSession(email, { password })
22
- } catch (err) {
23
- setError(err instanceof Error ? err.message : 'Não foi possível entrar.')
24
- } finally {
25
- setBusy(false)
26
- }
27
- }
28
-
29
- return (
30
- <div className="mx-auto max-w-sm rounded-xl border border-border bg-card p-7 shadow-sm">
31
- <h2 className="text-xl font-semibold text-foreground">{config.name}</h2>
32
- <p className="mt-1 text-sm text-muted-foreground">Entre para acessar seus cursos.</p>
33
- <form className="mt-5 space-y-3" onSubmit={submit}>
34
- <input
35
- data-testid="member-email"
36
- type="email"
37
- required
38
- placeholder="voce@exemplo.com"
39
- value={email}
40
- onChange={(e) => setEmail(e.target.value)}
41
- className="w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm"
42
- />
43
- <input
44
- data-testid="member-password"
45
- type="password"
46
- required
47
- placeholder="Senha"
48
- value={password}
49
- onChange={(e) => setPassword(e.target.value)}
50
- className="w-full rounded-md border border-input bg-background px-3 py-2.5 text-sm"
51
- />
52
- {error && <p data-testid="member-auth-error" className="text-sm text-destructive">{error}</p>}
53
- <button
54
- type="submit"
55
- data-testid="member-signin"
56
- disabled={busy}
57
- className="w-full rounded-md bg-primary py-2.5 font-semibold text-primary-foreground disabled:opacity-60"
58
- >
59
- {busy ? 'Entrando…' : 'Entrar'}
60
- </button>
61
- </form>
62
- </div>
63
- )
64
- }
65
-
66
- export function MyCoursesPage() {
67
- const session = useMemberSession()
68
- const { courses, loading } = useMyCourses(session.customerId)
69
-
70
- if (!session.customerId) {
71
- return (
72
- <main className="mx-auto max-w-3xl px-4 py-12 sm:px-6">
73
- <AuthForm />
74
- </main>
75
- )
76
- }
77
-
78
- return (
79
- <main className="mx-auto max-w-5xl px-4 py-8 sm:px-6">
80
- <h1 className="mb-1 text-2xl font-bold tracking-tight text-foreground">Meus cursos</h1>
81
- <p className="mb-6 text-sm text-muted-foreground">{session.email}</p>
82
-
83
- {loading ? (
84
- <div className="py-16 text-center text-muted-foreground">Carregando…</div>
85
- ) : courses.length === 0 ? (
86
- <div data-testid="member-empty" className="rounded-xl border border-border bg-card py-16 text-center text-muted-foreground">
87
- Você ainda não tem cursos.
88
- </div>
89
- ) : (
90
- <div data-testid="member-courses" className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
91
- {courses.map(({ course, progressPercent, completedLessons, totalLessons }) => (
92
- <button
93
- key={course.id}
94
- data-testid="member-course-card"
95
- data-slug={course.slug}
96
- onClick={() => navigateTo(`/course/${course.slug}`)}
97
- className="group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md"
98
- >
99
- <div className="aspect-video w-full overflow-hidden bg-muted">
100
- {course.thumbnailUrl && <img src={course.thumbnailUrl} alt="" className="h-full w-full object-cover" />}
101
- </div>
102
- <div className="space-y-2 p-4">
103
- <h3 className="font-semibold text-foreground line-clamp-2">{course.title}</h3>
104
- <div className="h-1.5 w-full overflow-hidden rounded-full bg-muted">
105
- <div className="h-full rounded-full bg-primary transition-all" style={{ width: `${progressPercent}%` }} />
106
- </div>
107
- <p className="text-xs text-muted-foreground">
108
- {completedLessons}/{totalLessons} aulas · {progressPercent}%
109
- </p>
110
- </div>
111
- </button>
112
- ))}
113
- </div>
114
- )}
115
- </main>
116
- )
117
- }
package/src/router.ts DELETED
@@ -1,45 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { hashRouterAdapter } from '@fayz-ai/core'
3
-
4
- // Minimal hash router for the member portal — the same lightweight approach the
5
- // storefront uses (no react-router; avoids singleton hazards under source-alias).
6
-
7
- const adapter = hashRouterAdapter()
8
-
9
- export function navigateTo(to: string): void {
10
- adapter.navigate(to)
11
- }
12
-
13
- export function useHashPath(): string {
14
- const [path, setPath] = useState(() => adapter.getCurrentPath() || '/')
15
- useEffect(() => {
16
- return adapter.onPathChange((p) => {
17
- setPath(p || '/')
18
- if (typeof window !== 'undefined') window.scrollTo(0, 0)
19
- })
20
- }, [])
21
- return path
22
- }
23
-
24
- export function matchPath(pattern: string, path: string): Record<string, string> | null {
25
- const cleanPath = (path.split('?')[0] ?? '').replace(/\/+$/, '') || '/'
26
- const patternParts = pattern.split('/').filter(Boolean)
27
- const pathParts = cleanPath.split('/').filter(Boolean)
28
- if (patternParts.length !== pathParts.length) return null
29
- const params: Record<string, string> = {}
30
- for (let i = 0; i < patternParts.length; i++) {
31
- const pat = patternParts[i]!
32
- const part = pathParts[i]!
33
- if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)
34
- else if (pat !== part) return null
35
- }
36
- return params
37
- }
38
-
39
- export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
40
- to: string
41
- }
42
-
43
- export function Link({ to, children, ...rest }: LinkProps) {
44
- return React.createElement('a', { href: `#${to}`, ...rest }, children)
45
- }
package/src/scaffold.tsx DELETED
@@ -1,73 +0,0 @@
1
- import React from 'react'
2
- import { registerScaffold, defineApp } from '@fayz-ai/core'
3
- import type { AppManifest } from '@fayz-ai/core'
4
- import { MemberConfigProvider, resolveConfig } from './config'
5
- import type { MemberConfig } from './config'
6
- import { initMemberRuntime, MemberShell } from './createMemberApp'
7
-
8
- // ---------------------------------------------------------------------------
9
- // Member scaffold — renders the learner portal from a pure-data AppManifest.
10
- // Mirrors the storefront scaffold's dual path: defineMember (config → manifest)
11
- // and MemberScaffold (manifest → render). This is the SDK's third surface type
12
- // (authenticated member area), alongside 'admin' and 'storefront'.
13
- // ---------------------------------------------------------------------------
14
-
15
- function slug(name: string): string {
16
- return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'app'
17
- }
18
-
19
- export function defineMember(config: MemberConfig): AppManifest {
20
- return defineApp({
21
- id: slug(config.name),
22
- name: config.name,
23
- backend: config.supabaseUrl ? { provider: 'supabase', url: config.supabaseUrl } : { provider: 'mock' },
24
- locale: { default: config.locale ?? 'pt-BR', supported: [config.locale ?? 'pt-BR'] },
25
- surfaces: {
26
- member: {
27
- scaffold: 'member',
28
- options: {
29
- logoUrl: config.logoUrl,
30
- accent: config.accent,
31
- autoEnroll: config.autoEnroll ?? true,
32
- },
33
- },
34
- },
35
- })
36
- }
37
-
38
- function manifestToMemberConfig(manifest: AppManifest, surfaceName: string): MemberConfig {
39
- const o = (manifest.surfaces[surfaceName]?.options ?? {}) as Record<string, unknown>
40
- return {
41
- name: manifest.name,
42
- locale: (manifest.locale as { default?: string } | undefined)?.default,
43
- logoUrl: o.logoUrl as string | undefined,
44
- accent: o.accent as string | undefined,
45
- autoEnroll: (o.autoEnroll as boolean | undefined) ?? true,
46
- supabaseUrl: manifest.backend?.provider === 'supabase' ? manifest.backend.url : undefined,
47
- }
48
- }
49
-
50
- export function MemberScaffold({ manifest, surface }: { manifest: AppManifest; surface: string }) {
51
- const config = React.useMemo(() => manifestToMemberConfig(manifest, surface), [manifest, surface])
52
- const resolved = React.useMemo(() => resolveConfig(config), [config])
53
- const inited = React.useRef(false)
54
- if (!inited.current) {
55
- initMemberRuntime(config)
56
- inited.current = true
57
- }
58
- return (
59
- <MemberConfigProvider value={resolved}>
60
- <MemberShell />
61
- </MemberConfigProvider>
62
- )
63
- }
64
- MemberScaffold.displayName = 'MemberScaffold'
65
-
66
- // Registration is lazy (called from the portal renderApp wrapper) so that
67
- // importing the @fayz-ai/portal barrel stays side-effect-free and tree-shakable.
68
- let memberScaffoldRegistered = false
69
- export function registerMemberScaffold(): void {
70
- if (memberScaffoldRegistered) return
71
- memberScaffoldRegistered = true
72
- registerScaffold('member', MemberScaffold, { source: 'sdk', label: 'Member' })
73
- }
package/src/session.ts DELETED
@@ -1,26 +0,0 @@
1
- import { create } from 'zustand'
2
- import { persist } from 'zustand/middleware'
3
-
4
- // Learner session — mirrors the auth identity for synchronous UI access. The
5
- // customerId is the key courses enrollments/progress are stored against.
6
-
7
- export interface MemberSessionState {
8
- customerId: string | null
9
- email: string | null
10
- name: string | null
11
- setSession(s: { customerId: string; email: string; name?: string | null }): void
12
- signOut(): void
13
- }
14
-
15
- export const useMemberSession = create<MemberSessionState>()(
16
- persist(
17
- (set) => ({
18
- customerId: null,
19
- email: null,
20
- name: null,
21
- setSession: ({ customerId, email, name }) => set({ customerId, email, name: name ?? null }),
22
- signOut: () => set({ customerId: null, email: null, name: null }),
23
- }),
24
- { name: 'fayz.portal.session.v1' },
25
- ),
26
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes