@@ -16,6 +16,8 @@ import { buildLibraryPageGoURL } from "../../../utils/projectHelpers";
1616import { Alert , Button , Divider , Heading , Link , NumberInput , Radio , RadioGroup , Stack , Text } from "@libretexts/davis-react" ;
1717import { IconBook2 , IconShoppingCartPlus } from "@tabler/icons-react" ;
1818import useStoreMaxQuantityPerItem from "../../../hooks/useStoreMaxQuantityPerItem" ;
19+ import useClientConfig from "../../../hooks/useClientConfig" ;
20+ import { getBookExportURL } from "../../../utils/bookExports" ;
1921
2022const BOOK_PAGE_LIMIT = 799 ;
2123
@@ -25,6 +27,7 @@ export default function ProductPage() {
2527 const { cart, addToCart, removeFromCart, loading : cartLoading } = useCart ( ) ;
2628 const params = useParams < { product_id : string } > ( ) ;
2729 const maxQuantityPerItem = useStoreMaxQuantityPerItem ( ) ;
30+ const { isProduction } = useClientConfig ( ) ;
2831 const { data : product , isFetching } = useQuery < StoreProduct > ( {
2932 queryKey : [ "store-product" , params . product_id ] ,
3033 queryFn : async ( ) => {
@@ -118,7 +121,11 @@ export default function ProductPage() {
118121 return num_pages > BOOK_PAGE_LIMIT ;
119122 } , [ product , isBook ] ) ;
120123
121- const [ bookDetail , setBookDetail ] = useState < { license ?: string ; [ key : string ] : unknown } | null > ( null ) ;
124+ const [ bookDetail , setBookDetail ] = useState < {
125+ license ?: string ;
126+ exportInfo ?: { customCoverOrg ?: string ; [ key : string ] : unknown } ;
127+ [ key : string ] : unknown ;
128+ } | null > ( null ) ;
122129
123130 useEffect ( ( ) => {
124131 if ( ! product || ! isBook ) return ;
@@ -141,6 +148,30 @@ export default function ProductPage() {
141148 return license . includes ( "cc-by-nc" ) || license . includes ( "ccbync" ) ;
142149 } , [ bookDetail ] ) ;
143150
151+ /**
152+ * The organization producing the book's custom cover, when it has one.
153+ * Display-only: the field is informational and never gates pricing or
154+ * fulfillment.
155+ */
156+ const customCoverOrg = useMemo ( ( ) => {
157+ if ( ! isBook ) return null ;
158+ const org = bookDetail ?. exportInfo ?. customCoverOrg ;
159+ return typeof org === "string" && org . trim ( ) ? org . trim ( ) : null ;
160+ } , [ bookDetail , isBook ] ) ;
161+
162+ /**
163+ * Cover artwork for the binding the customer currently has selected, so the
164+ * link always points at the PDF that would actually be printed.
165+ */
166+ const customCoverURL = useMemo ( ( ) => {
167+ if ( ! customCoverOrg ) return "" ;
168+ return getBookExportURL (
169+ product ?. metadata [ "book_id" ] ,
170+ hardcover ? "cover-casewrap" : "cover-perfectbound" ,
171+ { isProduction }
172+ ) ;
173+ } , [ customCoverOrg , product , hardcover , isProduction ] ) ;
174+
144175 function handleAddToCart ( ) {
145176 if ( ! product ) return ;
146177 if ( ! price || ! price . unit_amount ) {
@@ -308,6 +339,25 @@ export default function ProductPage() {
308339 error = { quantity > maxQuantityPerItem }
309340 errorMessage = { `Maximum ${ maxQuantityPerItem } per item. Please reduce the quantity to continue.` }
310341 />
342+ { customCoverOrg && (
343+ < Alert
344+ variant = "info"
345+ message = { `This book will ship with a custom cover by ${ customCoverOrg } .` }
346+ { ...( customCoverURL
347+ ? {
348+ action : {
349+ label : "View custom cover" ,
350+ onClick : ( ) =>
351+ window . open (
352+ customCoverURL ,
353+ "_blank" ,
354+ "noopener,noreferrer"
355+ ) ,
356+ } ,
357+ }
358+ : { } ) }
359+ />
360+ ) }
311361 { hasNCLicense && (
312362 < Alert variant = "warning" message = { "This book has a NonCommercial clause in its license and cannot be resold for profit." } />
313363 ) }
0 commit comments