@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.
- package/dist/{createMemberApp.d.ts → app/createMemberApp.d.ts} +1 -1
- package/dist/app/createMemberApp.d.ts.map +1 -0
- package/dist/app/router.d.ts.map +1 -0
- package/dist/{scaffold.d.ts → app/scaffold.d.ts} +1 -1
- package/dist/app/scaffold.d.ts.map +1 -0
- package/dist/data/auth.d.ts.map +1 -0
- package/dist/data/session.d.ts.map +1 -0
- package/dist/index.d.ts +10 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/views/AccountPage.d.ts.map +1 -0
- package/dist/{pages → views}/CoursePlayerPage.d.ts.map +1 -1
- package/dist/views/MyCoursesPage.d.ts.map +1 -0
- package/package.json +5 -7
- package/dist/auth.d.ts.map +0 -1
- package/dist/createMemberApp.d.ts.map +0 -1
- package/dist/pages/AccountPage.d.ts.map +0 -1
- package/dist/pages/MyCoursesPage.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/session.d.ts.map +0 -1
- package/src/auth.ts +0 -98
- package/src/components/MemberHeader.tsx +0 -57
- package/src/config.ts +0 -39
- package/src/createMemberApp.tsx +0 -66
- package/src/index.ts +0 -56
- package/src/pages/AccountPage.tsx +0 -39
- package/src/pages/CoursePlayerPage.tsx +0 -126
- package/src/pages/MyCoursesPage.tsx +0 -117
- package/src/router.ts +0 -45
- package/src/scaffold.tsx +0 -73
- package/src/session.ts +0 -26
- /package/dist/{router.d.ts → app/router.d.ts} +0 -0
- /package/dist/{auth.d.ts → data/auth.d.ts} +0 -0
- /package/dist/{session.d.ts → data/session.d.ts} +0 -0
- /package/dist/{pages → views}/AccountPage.d.ts +0 -0
- /package/dist/{pages → views}/CoursePlayerPage.d.ts +0 -0
- /package/dist/{pages → views}/MyCoursesPage.d.ts +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { MemberConfig } from '
|
|
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 '
|
|
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 './
|
|
10
|
-
export { CoursePlayerPage } from './
|
|
11
|
-
export { AccountPage } from './
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
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/
|
|
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.
|
|
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.
|
|
26
|
-
"@fayz-ai/auth": "^0.
|
|
27
|
-
"@fayz-ai/courses": "^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",
|
package/dist/auth.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/router.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/scaffold.d.ts.map
DELETED
|
@@ -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"}
|
package/dist/session.d.ts.map
DELETED
|
@@ -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
|
-
}
|
package/src/createMemberApp.tsx
DELETED
|
@@ -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
|