@flamingo-stack/openframe-frontend-core 0.0.396-snapshot.20260709212121 → 0.0.396
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/{chunk-IVXOR2IA.js → chunk-2PTEK3D6.js} +2 -2
- package/dist/{chunk-DB6CUWTL.cjs → chunk-6DGQPKVA.cjs} +26 -26
- package/dist/{chunk-DB6CUWTL.cjs.map → chunk-6DGQPKVA.cjs.map} +1 -1
- package/dist/{chunk-OAENYOKC.cjs → chunk-AODSKL5I.cjs} +9 -9
- package/dist/{chunk-OAENYOKC.cjs.map → chunk-AODSKL5I.cjs.map} +1 -1
- package/dist/{chunk-M6NPDNVB.cjs → chunk-CCV6Z3CL.cjs} +26 -26
- package/dist/{chunk-M6NPDNVB.cjs.map → chunk-CCV6Z3CL.cjs.map} +1 -1
- package/dist/{chunk-JNU3YPLP.js → chunk-EKYE2VJU.js} +4 -4
- package/dist/{chunk-J6TAK6HE.js → chunk-FAUC7EA3.js} +4 -4
- package/dist/{chunk-2OOZASQE.js → chunk-GA3R77TB.js} +2 -2
- package/dist/{chunk-VTR4QDL2.js → chunk-H4WIS6RJ.js} +2 -2
- package/dist/{chunk-HPQKX5IW.cjs → chunk-HEH47UTC.cjs} +15 -15
- package/dist/{chunk-HPQKX5IW.cjs.map → chunk-HEH47UTC.cjs.map} +1 -1
- package/dist/{chunk-XNAYZHPK.cjs → chunk-HNXZFKDQ.cjs} +62 -62
- package/dist/{chunk-XNAYZHPK.cjs.map → chunk-HNXZFKDQ.cjs.map} +1 -1
- package/dist/{chunk-T5F54MN4.cjs → chunk-HWJUWOXK.cjs} +12 -12
- package/dist/{chunk-T5F54MN4.cjs.map → chunk-HWJUWOXK.cjs.map} +1 -1
- package/dist/{chunk-K5FRWPWF.js → chunk-J2UHW4UM.js} +2 -2
- package/dist/{chunk-55SNCMFS.cjs → chunk-KRMGVMIO.cjs} +31 -31
- package/dist/{chunk-55SNCMFS.cjs.map → chunk-KRMGVMIO.cjs.map} +1 -1
- package/dist/{chunk-Z6PAD2UN.cjs → chunk-NAFW3LKI.cjs} +7 -7
- package/dist/{chunk-Z6PAD2UN.cjs.map → chunk-NAFW3LKI.cjs.map} +1 -1
- package/dist/{chunk-TTHTO5H5.js → chunk-NFRHBR7W.js} +4 -4
- package/dist/{chunk-3E4BDAHJ.js → chunk-OP3H7VB2.js} +2 -2
- package/dist/{chunk-ZFK4HHRX.js → chunk-PQXUY6SB.js} +2 -2
- package/dist/{chunk-43C5ECNT.cjs → chunk-RUYZ5QMK.cjs} +37 -37
- package/dist/{chunk-43C5ECNT.cjs.map → chunk-RUYZ5QMK.cjs.map} +1 -1
- package/dist/{chunk-D4F7ZLJG.js → chunk-STEM5CPV.js} +4 -4
- package/dist/{chunk-CK5UZLIN.js → chunk-SV7IJPIH.js} +5000 -5313
- package/dist/chunk-SV7IJPIH.js.map +1 -0
- package/dist/{chunk-WCDM226S.js → chunk-TIC3UE6L.js} +2 -2
- package/dist/{chunk-BB7TZ6KS.cjs → chunk-VSPNWVIE.cjs} +59 -59
- package/dist/{chunk-BB7TZ6KS.cjs.map → chunk-VSPNWVIE.cjs.map} +1 -1
- package/dist/{chunk-SBWTMJPQ.cjs → chunk-XQILJ7FF.cjs} +5 -5
- package/dist/{chunk-SBWTMJPQ.cjs.map → chunk-XQILJ7FF.cjs.map} +1 -1
- package/dist/{chunk-ZUKBC2ZX.cjs → chunk-Y36KVLBX.cjs} +798 -1111
- package/dist/chunk-Y36KVLBX.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/entity-cards/blog-card.d.ts +3 -6
- package/dist/components/chat/entity-cards/blog-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/case-study-card.d.ts +3 -7
- package/dist/components/chat/entity-cards/case-study-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts +3 -6
- package/dist/components/chat/entity-cards/customer-interview-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/index.d.ts +0 -3
- package/dist/components/chat/entity-cards/index.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/investor-update-card.d.ts +3 -6
- package/dist/components/chat/entity-cards/investor-update-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts +2 -4
- package/dist/components/chat/entity-cards/onboarding-guide-card.d.ts.map +1 -1
- package/dist/components/chat/entity-cards/program-card.d.ts +5 -17
- package/dist/components/chat/entity-cards/program-card.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -10
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +1 -9
- package/dist/components/chat/types/entities/program-types.d.ts +0 -23
- package/dist/components/chat/types/entities/program-types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/cards-strip.d.ts.map +1 -1
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/features/video-bites-strip.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +120 -128
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +9 -17
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +2 -10
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +1 -9
- package/dist/index.cjs +2 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -9
- package/package.json +1 -1
- package/src/components/chat/entity-cards/blog-card.tsx +7 -40
- package/src/components/chat/entity-cards/case-study-card.tsx +6 -32
- package/src/components/chat/entity-cards/customer-interview-card.tsx +6 -31
- package/src/components/chat/entity-cards/index.ts +0 -18
- package/src/components/chat/entity-cards/investor-update-card.tsx +2 -28
- package/src/components/chat/entity-cards/onboarding-guide-card.tsx +1 -30
- package/src/components/chat/entity-cards/program-card.tsx +17 -69
- package/src/components/chat/types/entities/program-types.ts +0 -34
- package/src/components/features/cards-strip.tsx +8 -122
- package/src/components/features/video-bites-strip.tsx +0 -4
- package/dist/chunk-CK5UZLIN.js.map +0 -1
- package/dist/chunk-ZUKBC2ZX.cjs.map +0 -1
- package/dist/components/chat/entity-cards/entity-portrait-card.d.ts +0 -59
- package/dist/components/chat/entity-cards/entity-portrait-card.d.ts.map +0 -1
- package/dist/components/chat/entity-cards/use-cover-image-fallback.d.ts +0 -26
- package/dist/components/chat/entity-cards/use-cover-image-fallback.d.ts.map +0 -1
- package/src/components/chat/entity-cards/entity-portrait-card.tsx +0 -201
- package/src/components/chat/entity-cards/use-cover-image-fallback.ts +0 -50
- /package/dist/{chunk-IVXOR2IA.js.map → chunk-2PTEK3D6.js.map} +0 -0
- /package/dist/{chunk-JNU3YPLP.js.map → chunk-EKYE2VJU.js.map} +0 -0
- /package/dist/{chunk-J6TAK6HE.js.map → chunk-FAUC7EA3.js.map} +0 -0
- /package/dist/{chunk-2OOZASQE.js.map → chunk-GA3R77TB.js.map} +0 -0
- /package/dist/{chunk-VTR4QDL2.js.map → chunk-H4WIS6RJ.js.map} +0 -0
- /package/dist/{chunk-K5FRWPWF.js.map → chunk-J2UHW4UM.js.map} +0 -0
- /package/dist/{chunk-TTHTO5H5.js.map → chunk-NFRHBR7W.js.map} +0 -0
- /package/dist/{chunk-3E4BDAHJ.js.map → chunk-OP3H7VB2.js.map} +0 -0
- /package/dist/{chunk-ZFK4HHRX.js.map → chunk-PQXUY6SB.js.map} +0 -0
- /package/dist/{chunk-D4F7ZLJG.js.map → chunk-STEM5CPV.js.map} +0 -0
- /package/dist/{chunk-WCDM226S.js.map → chunk-TIC3UE6L.js.map} +0 -0
|
@@ -17,7 +17,6 @@ import Image from '../../../embed-shims/next-image'
|
|
|
17
17
|
import { Card } from '../../ui/card'
|
|
18
18
|
import { cn } from '../../../utils/cn'
|
|
19
19
|
import type { CaseStudy } from '../../../types/case-study'
|
|
20
|
-
import { EntityPortraitCard } from './entity-portrait-card'
|
|
21
20
|
import { useEntityCardLink } from './use-entity-card-link'
|
|
22
21
|
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
23
22
|
import {
|
|
@@ -33,7 +32,10 @@ import {
|
|
|
33
32
|
COMPACT_CARD_TITLE,
|
|
34
33
|
COMPACT_CARD_TITLE_ROW,
|
|
35
34
|
} from '../utils/compact-card-classes'
|
|
36
|
-
|
|
35
|
+
|
|
36
|
+
const hideOnError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
|
37
|
+
;(e.currentTarget as HTMLImageElement).style.display = 'none'
|
|
38
|
+
}
|
|
37
39
|
|
|
38
40
|
export interface CaseStudyCardProps {
|
|
39
41
|
study: CaseStudy
|
|
@@ -46,15 +48,11 @@ export interface CaseStudyCardProps {
|
|
|
46
48
|
targetPlatform?: string | null
|
|
47
49
|
/** OG placeholder URL, used when `study.featured_image` is missing. */
|
|
48
50
|
placeholderUrl?: string | null
|
|
49
|
-
size?: 'default' | 'sm'
|
|
50
|
-
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
51
|
-
showTypeBadge?: boolean
|
|
51
|
+
size?: 'default' | 'sm'
|
|
52
52
|
className?: string
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
* same zone boxes (media aspect → 72px title → 60px person footer). */
|
|
57
|
-
export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
|
|
55
|
+
export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
|
|
58
56
|
if (size === 'sm') {
|
|
59
57
|
return (
|
|
60
58
|
<span className={COMPACT_CARD_SKELETON_OUTER}>
|
|
@@ -100,7 +98,6 @@ export function CaseStudyCard({
|
|
|
100
98
|
targetPlatform,
|
|
101
99
|
placeholderUrl: placeholderUrlProp,
|
|
102
100
|
size = 'default',
|
|
103
|
-
showTypeBadge = true,
|
|
104
101
|
className,
|
|
105
102
|
}: CaseStudyCardProps) {
|
|
106
103
|
const { target, rel } = useEntityCardLink({
|
|
@@ -148,29 +145,6 @@ export function CaseStudyCard({
|
|
|
148
145
|
)
|
|
149
146
|
}
|
|
150
147
|
|
|
151
|
-
if (size === 'portrait') {
|
|
152
|
-
// Rail/strip density — shared <EntityPortraitCard> shell.
|
|
153
|
-
return (
|
|
154
|
-
<EntityPortraitCard
|
|
155
|
-
href={href}
|
|
156
|
-
target={target}
|
|
157
|
-
rel={rel}
|
|
158
|
-
typeLabel={showTypeBadge ? 'Case Study' : undefined}
|
|
159
|
-
imageUrl={study.featured_image}
|
|
160
|
-
placeholderUrl={placeholderUrl}
|
|
161
|
-
imageAlt={study.msp?.name || study.title}
|
|
162
|
-
title={study.title}
|
|
163
|
-
person={{
|
|
164
|
-
name: study.user?.full_name || 'Anonymous',
|
|
165
|
-
avatarUrl: study.user?.avatar_url,
|
|
166
|
-
subtitle: study.msp?.name ?? null,
|
|
167
|
-
iconOverlayUrl: study.msp?.icon_url ?? null,
|
|
168
|
-
}}
|
|
169
|
-
className={className}
|
|
170
|
-
/>
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
148
|
return (
|
|
175
149
|
<a href={href} target={target} rel={rel} className={cn('block h-full', className)}>
|
|
176
150
|
<Card className="bg-ods-card border border-ods-border hover:border-ods-accent transition-colors p-6 flex flex-col gap-6 overflow-hidden">
|
|
@@ -13,7 +13,6 @@ import { Card } from '../../ui/card'
|
|
|
13
13
|
import { cn } from '../../../utils/cn'
|
|
14
14
|
import { Video } from 'lucide-react'
|
|
15
15
|
import type { CustomerInterview } from '../../../types/customer-interview'
|
|
16
|
-
import { EntityPortraitCard } from './entity-portrait-card'
|
|
17
16
|
import { useEntityCardLink } from './use-entity-card-link'
|
|
18
17
|
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
19
18
|
import {
|
|
@@ -29,7 +28,10 @@ import {
|
|
|
29
28
|
COMPACT_CARD_TITLE,
|
|
30
29
|
COMPACT_CARD_TITLE_ROW,
|
|
31
30
|
} from '../utils/compact-card-classes'
|
|
32
|
-
|
|
31
|
+
|
|
32
|
+
const hideOnError = (e: React.SyntheticEvent<HTMLImageElement>) => {
|
|
33
|
+
;(e.currentTarget as HTMLImageElement).style.display = 'none'
|
|
34
|
+
}
|
|
33
35
|
|
|
34
36
|
export interface CustomerInterviewCardProps {
|
|
35
37
|
interview: CustomerInterview
|
|
@@ -41,14 +43,11 @@ export interface CustomerInterviewCardProps {
|
|
|
41
43
|
targetPlatform?: string | null
|
|
42
44
|
/** OG placeholder URL fallback when `interview.featured_image` is missing. */
|
|
43
45
|
placeholderUrl?: string | null
|
|
44
|
-
size?: 'default' | 'sm'
|
|
45
|
-
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
46
|
-
showTypeBadge?: boolean
|
|
46
|
+
size?: 'default' | 'sm'
|
|
47
47
|
className?: string
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
|
|
50
|
+
export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
|
|
52
51
|
if (size === 'sm') {
|
|
53
52
|
return (
|
|
54
53
|
<span className={COMPACT_CARD_SKELETON_OUTER}>
|
|
@@ -99,7 +98,6 @@ export function CustomerInterviewCard({
|
|
|
99
98
|
targetPlatform,
|
|
100
99
|
placeholderUrl: placeholderUrlProp,
|
|
101
100
|
size = 'default',
|
|
102
|
-
showTypeBadge = true,
|
|
103
101
|
className,
|
|
104
102
|
}: CustomerInterviewCardProps) {
|
|
105
103
|
const { target, rel } = useEntityCardLink({
|
|
@@ -152,29 +150,6 @@ export function CustomerInterviewCard({
|
|
|
152
150
|
)
|
|
153
151
|
}
|
|
154
152
|
|
|
155
|
-
if (size === 'portrait') {
|
|
156
|
-
// Rail/strip density — shared <EntityPortraitCard> shell.
|
|
157
|
-
return (
|
|
158
|
-
<EntityPortraitCard
|
|
159
|
-
href={href}
|
|
160
|
-
target={target}
|
|
161
|
-
rel={rel}
|
|
162
|
-
typeLabel={showTypeBadge ? 'Customer Interview' : undefined}
|
|
163
|
-
imageUrl={interview.featured_image}
|
|
164
|
-
placeholderUrl={placeholderUrl}
|
|
165
|
-
imageAlt={interview.title}
|
|
166
|
-
title={interview.title}
|
|
167
|
-
person={{
|
|
168
|
-
name: interview.user?.full_name || 'Customer',
|
|
169
|
-
avatarUrl: interview.user?.avatar_url,
|
|
170
|
-
subtitle: interview.msp?.name ?? null,
|
|
171
|
-
iconOverlayUrl: interview.msp?.icon_url ?? null,
|
|
172
|
-
}}
|
|
173
|
-
className={className}
|
|
174
|
-
/>
|
|
175
|
-
)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
153
|
return (
|
|
179
154
|
<a href={href} target={target} rel={rel} className={cn('block h-full', className)}>
|
|
180
155
|
<Card className="bg-ods-card border border-ods-border hover:border-ods-accent transition-colors p-6 flex flex-col gap-6 overflow-hidden">
|
|
@@ -16,16 +16,6 @@ export {
|
|
|
16
16
|
type EntityAuthorCardProps,
|
|
17
17
|
} from './entity-author-card'
|
|
18
18
|
export { BlogImagePlaceholder } from './blog-image-placeholder'
|
|
19
|
-
export {
|
|
20
|
-
EntityPortraitCard,
|
|
21
|
-
type EntityPortraitCardProps,
|
|
22
|
-
type EntityPortraitPerson,
|
|
23
|
-
} from './entity-portrait-card'
|
|
24
|
-
export {
|
|
25
|
-
useCoverImageFallback,
|
|
26
|
-
hideOnError,
|
|
27
|
-
type CoverImageFallback,
|
|
28
|
-
} from './use-cover-image-fallback'
|
|
29
19
|
export { AdminContentCard } from './admin-content-card'
|
|
30
20
|
export { WhatIShippedCard, WhatIShippedCardSkeleton } from './what-i-shipped-card'
|
|
31
21
|
export type { WhatIShippedCardData, WhatIShippedCardProps } from './what-i-shipped-card'
|
|
@@ -143,11 +133,3 @@ export {
|
|
|
143
133
|
type ChatCardRenderOptions,
|
|
144
134
|
type ChatCardDispatchExtras,
|
|
145
135
|
} from './dispatch'
|
|
146
|
-
|
|
147
|
-
// Program author-ref + video-bites profile adapter — re-exported through the
|
|
148
|
-
// PACKAGE-EXPORTED chat barrel so the hub can value-import them (deep paths
|
|
149
|
-
// into chat/types/* are not in the package exports map and 404 at runtime).
|
|
150
|
-
export {
|
|
151
|
-
programItemToStripProfile,
|
|
152
|
-
type ProgramAuthorRef,
|
|
153
|
-
} from '../types/entities/program-types'
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
import React from 'react'
|
|
12
12
|
import { Calendar } from 'lucide-react'
|
|
13
13
|
import { AdminContentCard } from './admin-content-card'
|
|
14
|
-
import { EntityPortraitCard } from './entity-portrait-card'
|
|
15
14
|
import { formatInvestorUpdatePeriod, type InvestorUpdate } from '../types/entities/investor-update'
|
|
16
15
|
import { useEntityCardLink } from './use-entity-card-link'
|
|
17
16
|
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
@@ -39,14 +38,11 @@ export interface InvestorUpdateCardProps {
|
|
|
39
38
|
targetPlatform?: string | null
|
|
40
39
|
/** OG placeholder URL used when `update.featured_image` is missing. */
|
|
41
40
|
placeholderUrl?: string | null
|
|
42
|
-
size?: 'default' | 'sm'
|
|
43
|
-
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
44
|
-
showTypeBadge?: boolean
|
|
41
|
+
size?: 'default' | 'sm'
|
|
45
42
|
className?: string
|
|
46
43
|
}
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' | 'portrait' }) {
|
|
45
|
+
export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm' }) {
|
|
50
46
|
if (size === 'sm') {
|
|
51
47
|
return (
|
|
52
48
|
<span className={COMPACT_CARD_SKELETON_OUTER}>
|
|
@@ -87,7 +83,6 @@ export function InvestorUpdateCard({
|
|
|
87
83
|
targetPlatform,
|
|
88
84
|
placeholderUrl: placeholderUrlProp,
|
|
89
85
|
size = 'default',
|
|
90
|
-
showTypeBadge = true,
|
|
91
86
|
className,
|
|
92
87
|
}: InvestorUpdateCardProps) {
|
|
93
88
|
const { target, rel } = useEntityCardLink({
|
|
@@ -149,27 +144,6 @@ export function InvestorUpdateCard({
|
|
|
149
144
|
)
|
|
150
145
|
}
|
|
151
146
|
|
|
152
|
-
if (size === 'portrait') {
|
|
153
|
-
// Rail/strip density — shared <EntityPortraitCard> shell. The reporting
|
|
154
|
-
// period is the meaningful footer line (no fabricated author — no avatar
|
|
155
|
-
// renders an initial circle).
|
|
156
|
-
const periodText = formatInvestorUpdatePeriod(update.period_start, update.period_end)
|
|
157
|
-
return (
|
|
158
|
-
<EntityPortraitCard
|
|
159
|
-
href={href}
|
|
160
|
-
target={target}
|
|
161
|
-
rel={rel}
|
|
162
|
-
typeLabel={showTypeBadge ? 'Investor Update' : undefined}
|
|
163
|
-
imageUrl={update.featured_image}
|
|
164
|
-
placeholderUrl={placeholderUrl}
|
|
165
|
-
imageAlt={update.title ?? 'Investor update'}
|
|
166
|
-
title={update.title || `Update #${update.update_number ?? '?'}`}
|
|
167
|
-
person={periodText ? { name: periodText } : null}
|
|
168
|
-
className={className}
|
|
169
|
-
/>
|
|
170
|
-
)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
147
|
return (
|
|
174
148
|
<a href={href} target={target} rel={rel} className={`block h-full ${className ?? ''}`}>
|
|
175
149
|
<AdminContentCard
|
|
@@ -24,7 +24,6 @@ import { Clock, ExternalLink, GraduationCap, Play } from 'lucide-react'
|
|
|
24
24
|
import { cn } from '../../../utils/cn'
|
|
25
25
|
import { BlogImagePlaceholder } from './blog-image-placeholder'
|
|
26
26
|
import { EntityAuthorCard } from './entity-author-card'
|
|
27
|
-
import { EntityPortraitCard } from './entity-portrait-card'
|
|
28
27
|
import { formatDurationMMSS } from '../../../utils/format'
|
|
29
28
|
import type { OnboardingGuide } from '../types/entities/onboarding-guide'
|
|
30
29
|
import { useEntityCardLink } from './use-entity-card-link'
|
|
@@ -53,10 +52,7 @@ export interface OnboardingGuideCardProps {
|
|
|
53
52
|
targetPlatform?: string | null
|
|
54
53
|
/** OG placeholder URL used by the catalog + sm variants when no cover. */
|
|
55
54
|
placeholderUrl?: string | null
|
|
56
|
-
size?: 'catalog' | 'default' | 'sm'
|
|
57
|
-
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
58
|
-
showTypeBadge?: boolean
|
|
59
|
-
| 'default' | 'sm' | 'portrait'
|
|
55
|
+
size?: 'catalog' | 'default' | 'sm'
|
|
60
56
|
className?: string
|
|
61
57
|
}
|
|
62
58
|
|
|
@@ -173,7 +169,6 @@ export function OnboardingGuideCard({
|
|
|
173
169
|
targetPlatform,
|
|
174
170
|
placeholderUrl: placeholderUrlProp,
|
|
175
171
|
size = 'default',
|
|
176
|
-
showTypeBadge = true,
|
|
177
172
|
className,
|
|
178
173
|
}: OnboardingGuideCardProps) {
|
|
179
174
|
const { target, rel } = useEntityCardLink({
|
|
@@ -188,30 +183,6 @@ export function OnboardingGuideCard({
|
|
|
188
183
|
aspect: size === 'sm' ? 'square' : 'wide',
|
|
189
184
|
})
|
|
190
185
|
|
|
191
|
-
if (size === 'portrait') {
|
|
192
|
-
// Rail/strip density — shared <EntityPortraitCard> shell. Same cover
|
|
193
|
-
// chain as the catalog variant.
|
|
194
|
-
const coverImage =
|
|
195
|
-
guide.featured_image ||
|
|
196
|
-
guide.main_video_thumbnail ||
|
|
197
|
-
guide.og_image_url ||
|
|
198
|
-
null
|
|
199
|
-
return (
|
|
200
|
-
<EntityPortraitCard
|
|
201
|
-
href={href}
|
|
202
|
-
target={target}
|
|
203
|
-
rel={rel}
|
|
204
|
-
typeLabel={showTypeBadge ? 'Guide' : undefined}
|
|
205
|
-
imageUrl={coverImage}
|
|
206
|
-
placeholderUrl={placeholderUrl}
|
|
207
|
-
imageAlt={guide.title}
|
|
208
|
-
title={guide.title}
|
|
209
|
-
person={guide.section ? { name: guide.section } : null}
|
|
210
|
-
className={className}
|
|
211
|
-
/>
|
|
212
|
-
)
|
|
213
|
-
}
|
|
214
|
-
|
|
215
186
|
if (size === 'catalog') {
|
|
216
187
|
const coverImage =
|
|
217
188
|
guide.featured_image ||
|
|
@@ -2,18 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ProgramCard (pure presentation). Generic card for podcasts / webinars /
|
|
5
|
-
* events.
|
|
6
|
-
*
|
|
7
|
-
* (vertical rail/strip card).
|
|
8
|
-
*
|
|
9
|
-
* `portrait` exists because mixed-content rails MUST share ONE card anatomy
|
|
10
|
-
* (2026 card-UI practice: a rail mixes content types, never card layouts —
|
|
11
|
-
* mixing orientations/aspects in one scroller is the anti-pattern). It
|
|
12
|
-
* renders the SAME three zones as the other portrait entity cards
|
|
13
|
-
* (CaseStudyCard et al.): wide media slot → fixed 72px title zone → fixed
|
|
14
|
-
* 60px person/meta footer, p-6 / gap-6. The program's identity lives in the
|
|
15
|
-
* Azeret Mono title and the date · duration meta line — not in a different
|
|
16
|
-
* layout.
|
|
5
|
+
* events. Two densities — `default` (vertical detail) and `sm` (compact
|
|
6
|
+
* horizontal for chat-inline).
|
|
17
7
|
*
|
|
18
8
|
* The card writes NO click logic — callers wrap with their own anchor
|
|
19
9
|
* and pass the resolved detail URL via `href`.
|
|
@@ -44,19 +34,16 @@ import {
|
|
|
44
34
|
COMPACT_CARD_TITLE,
|
|
45
35
|
COMPACT_CARD_TITLE_ROW,
|
|
46
36
|
} from '../utils/compact-card-classes'
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
type ProgramMedia,
|
|
53
|
-
type ProgramHost,
|
|
37
|
+
import type {
|
|
38
|
+
BaseProgramItem,
|
|
39
|
+
ProgramConfig,
|
|
40
|
+
ProgramMedia,
|
|
41
|
+
ProgramHost,
|
|
54
42
|
} from '../types/entities/program-types'
|
|
55
|
-
import { EntityPortraitCard } from './entity-portrait-card'
|
|
56
43
|
import { useEntityCardLink } from './use-entity-card-link'
|
|
57
44
|
import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
|
|
58
45
|
|
|
59
|
-
type CardSize = 'default' | 'sm'
|
|
46
|
+
type CardSize = 'default' | 'sm'
|
|
60
47
|
|
|
61
48
|
/**
|
|
62
49
|
* Format a Date with date-fns pinned to UTC. `date-fns` `format()` reads the
|
|
@@ -128,8 +115,6 @@ export interface ProgramCardProps<T extends BaseProgramItem> {
|
|
|
128
115
|
media?: ProgramMedia[]
|
|
129
116
|
renderMeta?: (item: T) => React.ReactNode
|
|
130
117
|
size?: CardSize
|
|
131
|
-
/** Portrait density: render the content-type chip. Mixed rails only; single-type rails pass false. Default true. */
|
|
132
|
-
showTypeBadge?: boolean
|
|
133
118
|
/** Detail URL resolved by the caller. */
|
|
134
119
|
href: string
|
|
135
120
|
/** When `_blank`, opens in a new tab. Set by chat dispatch via
|
|
@@ -227,7 +212,6 @@ export function ProgramCard<T extends BaseProgramItem>({
|
|
|
227
212
|
media = [],
|
|
228
213
|
renderMeta,
|
|
229
214
|
size = 'default',
|
|
230
|
-
showTypeBadge = true,
|
|
231
215
|
href,
|
|
232
216
|
target: targetProp,
|
|
233
217
|
rel: relProp,
|
|
@@ -253,60 +237,24 @@ export function ProgramCard<T extends BaseProgramItem>({
|
|
|
253
237
|
const accentColor = 'var(--color-accent-primary)'
|
|
254
238
|
const isScheduled = 'status' in item && (item as any).status === 'scheduled'
|
|
255
239
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
240
|
+
if (size === 'sm') {
|
|
241
|
+
const itemDate = (() => {
|
|
242
|
+
try { return formatUtc(new Date(item.date), 'MMM d, yyyy') } catch { return '' }
|
|
243
|
+
})()
|
|
244
|
+
const compactCover = coverImage || placeholderUrl || null
|
|
245
|
+
let typeMeta: string | null = null
|
|
259
246
|
if (config.type === 'podcast' && 'duration_seconds' in item && !isScheduled) {
|
|
260
247
|
const dur = (item as any).duration_seconds
|
|
261
|
-
if (typeof dur === 'number' && dur > 0)
|
|
248
|
+
if (typeof dur === 'number' && dur > 0) typeMeta = formatDurationCompact(dur)
|
|
262
249
|
} else if (config.type === 'event' && 'location_name' in item) {
|
|
263
250
|
const loc = (item as any).location_name
|
|
264
|
-
if (typeof loc === 'string' && loc.trim().length > 0)
|
|
251
|
+
if (typeof loc === 'string' && loc.trim().length > 0) typeMeta = loc
|
|
265
252
|
} else if (config.type === 'webinar' && 'start_at' in item) {
|
|
266
253
|
const w = item as any
|
|
267
254
|
const time = formatTimeWithTimezone(w.start_at, w.timezone ?? null)
|
|
268
255
|
const dur = formatDurationFromRange(w.start_at, w.end_at)
|
|
269
|
-
|
|
256
|
+
typeMeta = dur ? `${time} · ${dur}` : time
|
|
270
257
|
}
|
|
271
|
-
return null
|
|
272
|
-
}
|
|
273
|
-
const compactDate = (() => {
|
|
274
|
-
try { return formatUtc(new Date(item.date), 'MMM d, yyyy') } catch { return '' }
|
|
275
|
-
})()
|
|
276
|
-
|
|
277
|
-
if (size === 'portrait') {
|
|
278
|
-
// Rail/strip density — mapped onto the shared <EntityPortraitCard> shell
|
|
279
|
-
// (media aspect-[1200/630] → h-[72px] title → h-[60px] footer, p-6/gap-6).
|
|
280
|
-
// Person = author-first via programItemToStripProfile (author → primary
|
|
281
|
-
// host); the date · duration meta line fills the subtitle when the
|
|
282
|
-
// profile has no job title.
|
|
283
|
-
const profile = programItemToStripProfile(item as { author?: ProgramAuthorRef | null; hosts?: ProgramHost[] | null })
|
|
284
|
-
const dateMeta = [compactDate, compactTypeMeta()].filter(Boolean).join(' · ')
|
|
285
|
-
return (
|
|
286
|
-
<EntityPortraitCard
|
|
287
|
-
href={href}
|
|
288
|
-
target={target}
|
|
289
|
-
rel={rel}
|
|
290
|
-
typeLabel={showTypeBadge ? config.labels.singular : undefined}
|
|
291
|
-
imageUrl={coverImage}
|
|
292
|
-
placeholderUrl={placeholderUrl}
|
|
293
|
-
imageAlt={item.title}
|
|
294
|
-
title={item.title}
|
|
295
|
-
titleClassName="font-['Azeret_Mono'] font-semibold text-lg leading-6"
|
|
296
|
-
person={
|
|
297
|
-
profile
|
|
298
|
-
? { name: profile.name, avatarUrl: profile.avatarUrl, subtitle: profile.subtitle || dateMeta }
|
|
299
|
-
: { name: config.labels.singular, subtitle: dateMeta }
|
|
300
|
-
}
|
|
301
|
-
className={className}
|
|
302
|
-
/>
|
|
303
|
-
)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (size === 'sm') {
|
|
307
|
-
const itemDate = compactDate
|
|
308
|
-
const compactCover = coverImage || placeholderUrl || null
|
|
309
|
-
const typeMeta = compactTypeMeta()
|
|
310
258
|
const subtitleParts = [itemDate, typeMeta, config.labels?.singular].filter(
|
|
311
259
|
(s): s is string => typeof s === 'string' && s.length > 0,
|
|
312
260
|
)
|
|
@@ -17,10 +17,6 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { ReactNode } from 'react';
|
|
20
|
-
import {
|
|
21
|
-
toStripProfile,
|
|
22
|
-
type VideoBiteStripProfile,
|
|
23
|
-
} from '../../../features/video-bites-shared';
|
|
24
20
|
|
|
25
21
|
// ============================================================================
|
|
26
22
|
// HOST TYPES
|
|
@@ -38,36 +34,6 @@ export interface ProgramHost {
|
|
|
38
34
|
role?: string; // e.g., "Host", "Speaker", "Guest"
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
/**
|
|
42
|
-
* Client-safe author subset hydrated onto program items from the entity's
|
|
43
|
-
* `author_id → profiles` FK join (podcast episodes / webinars). Deliberately
|
|
44
|
-
* excludes `email` — this shape is serialized into public payloads.
|
|
45
|
-
*/
|
|
46
|
-
export interface ProgramAuthorRef {
|
|
47
|
-
id?: string | null;
|
|
48
|
-
full_name?: string | null;
|
|
49
|
-
avatar_url?: string | null;
|
|
50
|
-
job_title?: string | null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Video-bites overlay profile for a program item: internal author first
|
|
55
|
-
* (`author_id → profiles`), then the provider-synced primary host (Livestorm
|
|
56
|
-
* speakers / Podbean hosts). Host `role` is a raw provider enum (e.g.
|
|
57
|
-
* `team_member`) — never display copy, so it is not used as the subtitle.
|
|
58
|
-
* Routes through `toStripProfile` (the SSoT — null on missing names).
|
|
59
|
-
*/
|
|
60
|
-
export function programItemToStripProfile(item: {
|
|
61
|
-
author?: ProgramAuthorRef | null;
|
|
62
|
-
hosts?: ProgramHost[] | null;
|
|
63
|
-
}): VideoBiteStripProfile | null {
|
|
64
|
-
const fromAuthor = toStripProfile(item.author);
|
|
65
|
-
if (fromAuthor) return fromAuthor;
|
|
66
|
-
const host = Array.isArray(item.hosts) ? item.hosts[0] : null;
|
|
67
|
-
if (!host?.name) return null;
|
|
68
|
-
return toStripProfile({ full_name: host.name, avatar_url: host.avatar_url ?? null });
|
|
69
|
-
}
|
|
70
|
-
|
|
71
37
|
// ============================================================================
|
|
72
38
|
// DATABASE ROW TYPES
|
|
73
39
|
// ============================================================================
|