Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement →
Sign In

@wface/pixel-cli

Package Overview
Dependencies
Maintainers
3
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wface/pixel-cli - npm Package Compare versions

Comparing version
0.3.0
to
0.3.1
+73
src/components/blocks/entity-card/EntityCard.module.scss
.entityCard {
max-width: 320px;
padding: 8px;
border-radius: var(--sizing-16);
background: var(--purple-600);
.entityHeader {
background: var(--background-accent-purple-subtlest-default);
border-radius: var(--sizing-12);
padding: 24px 20px;
.plan {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 44px;
.planTitle {
display: flex;
align-items: center;
gap: 4px;
.icon {
display: flex;
align-items: center;
justify-content: center;
}
.title {
color: var(--text-default);
}
}
.tag {
}
}
.entityPrice {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 20px;
.discount {
color: var(--text-subtlest);
}
}
.desc {
margin-bottom: 20px;
p {
color: var(--text-subtlest);
}
}
}
.includes {
padding: 24px 20px;
ul {
li {
display: flex;
gap: 12px;
color: var(--text-white);
font-size: 14px;
line-height: 20px;
margin-bottom: 12px;
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
}
&:last-child {
margin-bottom: 0;
}
}
}
}
}
export type JsonType = {
TAG?: string;
GORUNEN_ISIM: string
OZEL_ALAN?: string
ACIKLAMA: string
Title: string
Billing_Period: string
BUTTON_LABEL: string
FOOTER_TEXT: string
SORT_ORDER: string
OFFER_TYPE_NAME: string
OFFER_GEO_LOCATION_ID: string
OFFER_CUSTOMER_STATU: string
OFFER_IS_VIP: string
OFFER_IS_DISCOUNT_PACKAGE: string
OFFER_IS_FREE_PACKAGE: string
OFFER_APPLICATION_ID: string
OFFER_GATEWAY: string
UCRETSIZ_PAKET_MI: string
}
export type Entity = {
OfferSrelId: number
EntityJsonTypeList: string
OfferCd: string
OfferType: string
OfferDescription: string
PriceAmount: number
CurrencyTypeCd: string
}
export type Props = {
entity: Entity
}
import {useMemo} from "react";
import styles from './EntityCard.module.scss';
import type { JsonType, Props } from './EntityCard.types';
import {PiBadge, PiBody, PiButton, PiHeading} from "@wface/pixel-ui";
import {FiCheck, FiCreditCard} from "react-icons/fi";
const EntityCard = (props: Props) => {
const { entity } = props;
const getParsedData = (data: string) => {
if (!data) return null;
return JSON.parse(data);
}
const parsedEntity = useMemo<JsonType>(() => getParsedData(entity.EntityJsonTypeList), [entity.EntityJsonTypeList]);
const includes = useMemo<string[]>(() => {
if (!parsedEntity) return [];
return parsedEntity.FOOTER_TEXT.split(';').map((item) => item.trim());
}, [parsedEntity]);
return (
<div className={styles.entityCard}>
<div className={styles.entityHeader}>
<div className={styles.plan}>
<div className={styles.planTitle}>
<span className={styles.icon}><FiCreditCard /></span>
<PiBody className={styles.title} as="span" size="lg" weight="medium">{parsedEntity.Title}</PiBody>
</div>
{
parsedEntity?.TAG && <PiBadge filled variant="orange" content={parsedEntity.TAG}></PiBadge>
}
</div>
<div className={styles.entityPrice}>
<PiHeading variant="h2" as="span" className={styles.price}>
{`${entity.PriceAmount} ${entity.CurrencyTypeCd}`}
</PiHeading>
{
parsedEntity?.OZEL_ALAN && (
<PiBody size="md" as="span" weight="regular" className={styles.discount}>
/ {`${parsedEntity?.OZEL_ALAN} ${entity.CurrencyTypeCd}`}
</PiBody>
)
}
</div>
<div className={styles.desc}>
<PiBody size="sm" weight="regular">{parsedEntity.ACIKLAMA}</PiBody>
</div>
<div className={styles.button}>
<PiButton fullWidth>{parsedEntity.BUTTON_LABEL}</PiButton>
</div>
</div>
<div className={styles.includes}>
<ul>
{includes.map(i => (
<li key={i} className={styles.entityItem}>
<span className={styles.icon}><FiCheck size={24} /></span>
{i}
</li>
))}
</ul>
</div>
</div>
);
};
export default EntityCard;
export const entity = {
OfferSrelId: 118868,
EntityJsonTypeList: "{\"GORUNEN_ISIM\":\"1 week FREE trial for new customers\",\"ACIKLAMA\":\"From 11th August, recurring monthly plan after free trial period renews at $15.99. Card fees may apply.\",\"Title\":\"MONTHLY PLAN\",\"Billing_Period\":\"Billed Monthly\",\"BUTTON_LABEL\":\"Subscribe\",\"FOOTER_TEXT\":\"Watch LaLiga, Serie A, Bundesliga, Carabao Cup, EFL Championship, SPFL and more;ATP and WTA Tour, Davis Cup;Live, On-Demand, Highlights & Mini-Matches;Available on iOS, Android mobile and tablet, Samsung TVs, LG TVs, AndroidTV, Web browser, No lock-in contract\",\"SORT_ORDER\":\"999\",\"OFFER_TYPE_NAME\":\"Subscription\",\"OFFER_GEO_LOCATION_ID\":\"5110\",\"OFFER_CUSTOMER_STATU\":\"POTANSIYEL\",\"OFFER_IS_VIP\":\"false\",\"OFFER_IS_DISCOUNT_PACKAGE\":\"false\",\"OFFER_IS_FREE_PACKAGE\":\"true\",\"OFFER_APPLICATION_ID\":\"4\",\"OFFER_GATEWAY\":\"INGENICO\",\"UCRETSIZ_PAKET_MI\":\"true\"}",
OfferCd: "AIMAN1",
OfferType: "Subscription",
OfferDescription: "Australia Monthly Pass (w Free Trial)",
PriceAmount: 15.99,
CurrencyTypeCd: "AUD"
}
.exampleList {
.title {
display: flex;
align-items: center;
cursor: pointer;
.expand {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
transition: .3s;
&.active {
transform: rotate(90deg);
}
}
}
}
export interface Content {
ContentId: number
ContentSpecId: number
CmsContentId: string
ContentName: string
LongName: string
DisplayName: string
Description: string
CreationDate: string
CreatedBy: any
ContentSpecCd: string
ContentSpecName: string
ChannelName: string
CategoryName: string
IsLiveToVod: boolean
LiveToVodStatus: string
Type: string
HasWarning: boolean
contentUsageClass: ContentUsageClass[]
}
export interface ContentUsageClass {
CmsContentId: string
ContentId: number
DisplayName: string
UsageSpecCodeCd: string
UsageSpecId: number
EventStartTime: string
EventEndTime: string
EventShowTime: string
UsageSpecStatusTypeCd: string
UsageSpecStatusTypeName: string
ContentCategoryCd: string
OfferType: string
Type: string
HasWarning: boolean
contentUsageItemModel: ContentUsageItemModel[]
BroadcastStatu: boolean
}
export interface ContentUsageItemModel {
HasWarning: boolean
Type: string
DisplayName: string
IbmsServiceSpecCd: string
State: number
Status: string
LastPublishDate: any
}
import {PiBadge, PiButton, PiSkeleton, PiSwitch, PiTable, PiCountryFlag, PiDropdown } from "@wface/pixel-ui";
import {FiChevronRight, FiMoreVertical, FiEdit, FiArrowRightCircle, FiPlus, FiTrash} from "react-icons/fi";
import {useEffect, useState} from "react";
import {fetchData} from "./mockServer";
import styles from './ExampleList.module.scss';
import type { Content } from "./ExampleList.types.ts";
const SubTable = () => {
return (
<PiTable subTable>
<PiTable.TableHeader>
<PiTable.TableRow>
<PiTable.TableCell>Region</PiTable.TableCell>
<PiTable.TableCell>License Starts</PiTable.TableCell>
<PiTable.TableCell>License Ends</PiTable.TableCell>
<PiTable.TableCell>Broadcast</PiTable.TableCell>
<PiTable.TableCell>Status</PiTable.TableCell>
<PiTable.TableCell textAlign="right">Actions</PiTable.TableCell>
</PiTable.TableRow>
</PiTable.TableHeader>
<PiTable.TableBody>
<PiTable.TableRow>
<PiTable.TableCell>
<div style={{display: "flex", alignItems: "center", gap: '8px'}}>
{PiCountryFlag(221, '24px', '24px')}
Hong Kong
</div>
</PiTable.TableCell>
<PiTable.TableCell>2020-05-17 10:55</PiTable.TableCell>
<PiTable.TableCell>2020-05-17 10:55</PiTable.TableCell>
<PiTable.TableCell><PiSwitch checked onChange={() => null} label="On"/></PiTable.TableCell>
<PiTable.TableCell><PiBadge variant="success" content="Valid"/></PiTable.TableCell>
<PiTable.TableCell>
<div style={{display: 'flex', justifyContent: 'flex-end'}}>
<PiButton iconButton variant="secondary"><FiMoreVertical/></PiButton>
</div>
</PiTable.TableCell>
</PiTable.TableRow>
</PiTable.TableBody>
</PiTable>
)
}
const TableRowItem = (props: { row: Content, activeRowId: number | null, handleSubTable: (value: number) => void }) => {
const {row, activeRowId, handleSubTable} = props;
const [dropdown, setDropdown] = useState<boolean>(false);
const dropdownMenuList = () => {
return [
{
label: "Edit Content",
icon: <FiEdit />,
onClick: () => null,
},
{
label: "Update Content Metadata",
icon: <FiArrowRightCircle />,
onClick: () => null,
},
{
line: true
},
{
title: 'MANAGE COUNTRIES',
},
{
label: "Edit Countries",
icon: <FiPlus />,
onClick: () => null
},
{
label: "Delete Content",
icon: <FiTrash />,
onClick: () => null,
},
];
};
return (
<PiTable.TableRow subTable={<SubTable/>} showSubTable={row.ContentId === activeRowId}>
<PiTable.TableCell width={300}>
<div className={styles.title} onClick={() => handleSubTable(row.ContentId)}>
<span className={`${styles.expand} ${activeRowId === row?.ContentId ? styles.active : ""}`}>
<FiChevronRight size={16}/>
</span>
<span className={styles.title}>
{row.CmsContentId}
</span>
</div>
</PiTable.TableCell>
<PiTable.TableCell width={300}>{row.Description}</PiTable.TableCell>
<PiTable.TableCell width={200}>{row.ContentName}</PiTable.TableCell>
<PiTable.TableCell>{row.CategoryName}</PiTable.TableCell>
<PiTable.TableCell>{row.ChannelName}</PiTable.TableCell>
<PiTable.TableCell><PiBadge filled variant="success" content={row.LiveToVodStatus}/></PiTable.TableCell>
<PiTable.TableCell width={80} textAlign="right">
<PiDropdown minWidth={280} menuList={dropdownMenuList()} show={dropdown} setShow={setDropdown}/>
</PiTable.TableCell>
</PiTable.TableRow>
)
}
const ExampleList = () => {
const [activeRowId, setActiveRowId] = useState<number | null>(null);
const [data, setData] = useState<Content[]>([]);
const [loading, setLoading] = useState<boolean>(false)
const handleSubTable = (id: number) => {
if (id !== activeRowId) {
setActiveRowId(id)
} else {
setActiveRowId(null)
}
}
const getData = async () => {
setLoading(true);
try {
const res = await fetchData();
setData(res);
} catch (e) {
console.log(e)
setData([]);
} finally {
setLoading(false);
}
}
useEffect(() => {
getData();
}, []);
return (
<div className={styles.exampleList}>
{
loading ? (
<>
{
Array.from({length: 5}).map(() => (
<PiSkeleton
height={60}
variant="rectangular"
animation="wave"
style={{borderRadius: 12, marginBottom: 5}}
/>
))
}
</>
) : (
<PiTable>
<PiTable.TableHeader>
<PiTable.TableRow>
<PiTable.TableCell width={300}>CMS CONTENT ID</PiTable.TableCell>
<PiTable.TableCell width={300}>EPISODE TITLE</PiTable.TableCell>
<PiTable.TableCell width={200}>PLANNING TITLE</PiTable.TableCell>
<PiTable.TableCell>CATEGORY</PiTable.TableCell>
<PiTable.TableCell>CHANNEL</PiTable.TableCell>
<PiTable.TableCell> LIVE TO VOD STATUS</PiTable.TableCell>
<PiTable.TableCell width={80}> </PiTable.TableCell>
</PiTable.TableRow>
</PiTable.TableHeader>
<PiTable.TableBody>
{
data.map((row) => {
return (
<TableRowItem key={row.ContentId} row={row} handleSubTable={handleSubTable} activeRowId={activeRowId} />
)
})
}
</PiTable.TableBody>
</PiTable>
)
}
</div>
);
};
export default ExampleList;
import type {Content} from "./ExampleList.types.ts";
const data: Content[] = [
{
ContentId: 362099,
ContentSpecId: 42,
CmsContentId: "164_MPV000149869",
ContentName: "LaLiga",
LongName: "LaLiga",
DisplayName: "LaLiga",
Description: "Real Sociedad vs Atletico Madrid",
CreationDate: "2025-12-22T06:31:59",
CreatedBy: null,
ContentSpecCd: "SINGLE_EVENT",
ContentSpecName: "SINGLE_EVENT",
ChannelName: "beIN SPORTS MAX 4",
CategoryName: "FOOTBALL",
IsLiveToVod: true,
LiveToVodStatus: "Waiting",
Type: "CONTENT",
HasWarning: false,
contentUsageClass: [
{
CmsContentId: "164_MPV000149869",
ContentId: 362099,
DisplayName: "VOD_BS_NZ",
UsageSpecCodeCd: "VOD_BS_NZ",
UsageSpecId: 1287994,
EventStartTime: "2026-01-04T22:27:00",
EventEndTime: "2026-01-18T19:47:00",
EventShowTime: "1/4/2026 10:27 PM - 1/18/2026 7:47 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_MOBILE",
IbmsServiceSpecCd: "BS_NZ_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_WEB",
IbmsServiceSpecCd: "BS_NZ_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
}
]
},
{
ContentId: 362121,
ContentSpecId: 42,
CmsContentId: "166_MP000150154",
ContentName: "LaLiga 2",
LongName: "LaLiga 2",
DisplayName: "LaLiga 2",
Description: "Deportivo vs Cadiz",
CreationDate: "2025-12-22T06:32:03",
CreatedBy: null,
ContentSpecCd: "SINGLE_EVENT",
ContentSpecName: "SINGLE_EVENT",
ChannelName: "beIN SPORTS MAX 6",
CategoryName: "FOOTBALL",
IsLiveToVod: true,
LiveToVodStatus: "Waiting",
Type: "CONTENT",
HasWarning: false,
contentUsageClass: [
{
CmsContentId: "166_MP000150154",
ContentId: 362121,
DisplayName: "VOD_BS_AU",
UsageSpecCodeCd: "VOD_BS_AU",
UsageSpecId: 1288024,
EventStartTime: "2026-01-04T22:22:00",
EventEndTime: "2026-01-18T19:52:00",
EventShowTime: "1/4/2026 10:22 PM - 1/18/2026 7:52 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_AU_MOBILE",
IbmsServiceSpecCd: "BS_AU_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_AU_WEB",
IbmsServiceSpecCd: "BS_AU_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "166_MP000150154",
ContentId: 362121,
DisplayName: "VOD_BS_NZ",
UsageSpecCodeCd: "VOD_BS_NZ",
UsageSpecId: 1288023,
EventStartTime: "2026-01-04T22:22:00",
EventEndTime: "2026-01-18T19:52:00",
EventShowTime: "1/4/2026 10:22 PM - 1/18/2026 7:52 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_MOBILE",
IbmsServiceSpecCd: "BS_NZ_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_WEB",
IbmsServiceSpecCd: "BS_NZ_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
}
]
},
{
ContentId: 362084,
ContentSpecId: 42,
CmsContentId: "162_MPV000153422",
ContentName: "Serie A",
LongName: "Serie A",
DisplayName: "Serie A",
Description: "Inter vs Bologna",
CreationDate: "2025-12-22T06:31:38",
CreatedBy: null,
ContentSpecCd: "SINGLE_EVENT",
ContentSpecName: "SINGLE_EVENT",
ChannelName: "beIN SPORTS MAX 2",
CategoryName: "FOOTBALL",
IsLiveToVod: true,
LiveToVodStatus: "Waiting",
Type: "CONTENT",
HasWarning: false,
contentUsageClass: [
{
CmsContentId: "162_MPV000153422",
ContentId: 362084,
DisplayName: "VOD_BS_NZ",
UsageSpecCodeCd: "VOD_BS_NZ",
UsageSpecId: 1287977,
EventStartTime: "2026-01-04T22:10:00",
EventEndTime: "2026-01-18T19:30:00",
EventShowTime: "1/4/2026 10:10 PM - 1/18/2026 7:30 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_WEB",
IbmsServiceSpecCd: "BS_NZ_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_MOBILE",
IbmsServiceSpecCd: "BS_NZ_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
}
]
},
{
ContentId: 362111,
ContentSpecId: 42,
CmsContentId: "167_MP000138387",
ContentName: "AFCON 2025",
LongName: "AFCON 2025",
DisplayName: "AFCON 2025",
Description: "Episode 40 - Rd of 16",
CreationDate: "2025-12-22T06:32:45",
CreatedBy: null,
ContentSpecCd: "SINGLE_EVENT",
ContentSpecName: "SINGLE_EVENT",
ChannelName: "beIN SPORTS MAX 7",
CategoryName: "FOOTBALL",
IsLiveToVod: true,
LiveToVodStatus: "Waiting",
Type: "CONTENT",
HasWarning: false,
contentUsageClass: [
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_MY",
UsageSpecCodeCd: "VOD_BS_MY",
UsageSpecId: 1288179,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_MY_WEB",
IbmsServiceSpecCd: "BS_MY_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_MY_MOBILE",
IbmsServiceSpecCd: "BS_MY_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_SG",
UsageSpecCodeCd: "VOD_BS_SG",
UsageSpecId: 1288177,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_SG_MOBILE",
IbmsServiceSpecCd: "BS_SG_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_SG_WEB",
IbmsServiceSpecCd: "BS_SG_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_PH",
UsageSpecCodeCd: "VOD_BS_PH",
UsageSpecId: 1288178,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_PH_WEB",
IbmsServiceSpecCd: "BS_PH_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_PH_MOBILE",
IbmsServiceSpecCd: "BS_PH_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_NZ",
UsageSpecCodeCd: "VOD_BS_NZ",
UsageSpecId: 1288012,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_MOBILE",
IbmsServiceSpecCd: "BS_NZ_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_NZ_WEB",
IbmsServiceSpecCd: "BS_NZ_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_HK",
UsageSpecCodeCd: "VOD_BS_HK",
UsageSpecId: 1288181,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_HK_MOBILE",
IbmsServiceSpecCd: "BS_HK_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_HK_WEB",
IbmsServiceSpecCd: "BS_HK_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_TH",
UsageSpecCodeCd: "VOD_BS_TH",
UsageSpecId: 1288176,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_TH_WEB",
IbmsServiceSpecCd: "BS_TH_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_TH_MOBILE",
IbmsServiceSpecCd: "BS_TH_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
},
{
CmsContentId: "167_MP000138387",
ContentId: 362111,
DisplayName: "VOD_BS_ID",
UsageSpecCodeCd: "VOD_BS_ID",
UsageSpecId: 1288180,
EventStartTime: "2026-01-04T22:03:00",
EventEndTime: "2026-01-18T18:48:00",
EventShowTime: "1/4/2026 10:03 PM - 1/18/2026 6:48 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_ID_MOBILE",
IbmsServiceSpecCd: "BS_ID_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_ID_WEB",
IbmsServiceSpecCd: "BS_ID_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
}
]
},
{
ContentId: 361588,
ContentSpecId: 42,
CmsContentId: "29_MP000149869",
ContentName: "LaLiga",
LongName: "LaLiga",
DisplayName: "LaLiga",
Description: "Real Sociedad vs Atletico Madrid",
CreationDate: "2025-12-21T00:22:21",
CreatedBy: null,
ContentSpecCd: "SINGLE_EVENT",
ContentSpecName: "SINGLE_EVENT",
ChannelName: "beIN Sports 1",
CategoryName: "FOOTBALL",
IsLiveToVod: true,
LiveToVodStatus: "Waiting",
Type: "CONTENT",
HasWarning: false,
contentUsageClass: [
{
CmsContentId: "29_MP000149869",
ContentId: 361588,
DisplayName: "VOD_BS_SG",
UsageSpecCodeCd: "VOD_BS_SG",
UsageSpecId: 1287091,
EventStartTime: "2026-01-04T22:00:00",
EventEndTime: "2026-01-18T19:55:00",
EventShowTime: "1/4/2026 10:00 PM - 1/18/2026 7:55 PM",
UsageSpecStatusTypeCd: "GECERLI",
UsageSpecStatusTypeName: "Valid",
ContentCategoryCd: "FOOTBALL",
OfferType: "VOD",
Type: "USAGE_SPEC",
HasWarning: false,
contentUsageItemModel: [
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_SG_WEB",
IbmsServiceSpecCd: "BS_SG_WEB",
State: 2,
Status: "Puplished",
LastPublishDate: null
},
{
HasWarning: false,
Type: "USAGE_SPEC_ITEM",
DisplayName: "BS_SG_MOBILE",
IbmsServiceSpecCd: "BS_SG_MOBILE",
State: 2,
Status: "Puplished",
LastPublishDate: null
}
],
BroadcastStatu: true
}
]
},
];
export const fetchData = (): Promise<Content[]> => {
return new Promise(resolve => {
setTimeout(() => {
resolve(data);
}, 300)
})
}
import styles from '../EpgCalendar.module.scss'
import type {EpgData} from "../../../programmingGuide.types.ts";
type Props = {
data: EpgData
}
const Channel = (props: Props) => {
const { data } = props;
return (
<div className={styles.channel}>
<span className={styles.code}>{ data.CmsChannelId }</span>
<span className={styles.img}>
<img src={data.ChannelImagePath} alt={data.DisplayName} />
</span>
</div>
);
};
export default Channel;
.epgCard {
border-right: 1px solid var(--border-default);
height: 100%;
min-height: 90px;
padding: 6px;
background: var(--elevation-surface);
.header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
.tags {
display: flex;
align-items: center;
gap: 4px;
.tag {
display: flex;
align-items: center;
justify-content: center;
padding: 0 2px;
border-radius: 4px;
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
}
&.liveToVod {
background: var(--yellow-100);
color: var(--yellow-700);
}
&.videoTag {
background: var(--purple-100);
color: var(--purple-700);;
}
&.live {
background: var(--red-100);
.icon {
span {
display: block;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--red-700);
}
}
}
}
}
.actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
}
}
.info {
.time, .name {
color: var(--text-default);
}
.program {
color: var(--text-subtlest);
margin-top: 8px;
}
}
}
import styles from './EpgCard.module.scss'
import type {Epg} from "../../../../programmingGuide.types.ts";
import {PiBody} from "@wface/pixel-ui";
import {FiEye, FiHeart, FiLock, FiMoreVertical} from "react-icons/fi";
type Props = {
epg: Epg
}
const getTime = (timeValue: string, ampm: boolean = false) => {
const time = new Date(timeValue);
const hours = time.getUTCHours().toString().padStart(2, "0");
const minutes = time.getUTCMinutes().toString().padStart(2, "0");
let ampmValue: string = '';
if (ampm) {
ampmValue = Number(hours) >= 12 ? "pm" : "am";
}
return `${hours}:${minutes} ${ampmValue}`;
};
const EpgCard = (props: Props) => {
const { epg } = props;
return (
<div className={styles.epgCard}>
<div className={styles.header}>
<div className={styles.tags}>
{!epg?.IsLive ? <span className={`${styles.tag} ${styles.live}`}><span className={styles.icon}><span/></span></span> : null}
{!epg?.LiveToVod ? <span className={`${styles.tag} ${styles.liveToVod}`}><span className={styles.icon}><FiEye size={12} /></span> </span> : null}
{epg?.VideoTag ? <span className={`${styles.tag} ${styles.videoTag}`}><span className={styles.icon}><FiHeart size={12} /></span> </span> : null}
</div>
<div className={styles.actions}>
{!epg?.EpgLock ? <span className={styles.epgLock}><FiLock size={12}/></span> : null}
<span className={styles.dropdown}>
<FiMoreVertical size={12} />
</span>
</div>
</div>
<div className={styles.info}>
<PiBody className={styles.time} size="sm" weight="medium">{`${getTime(epg.StartTimeUtc)} - ${getTime(epg.EndTimeUtc)}`}</PiBody>
<PiBody className={styles.name} size="sm" weight="regular" truncate>{epg.EventName}</PiBody>
<PiBody className={styles.program} size="sm" weight="light" truncate>{epg.ProgramName}</PiBody>
</div>
</div>
);
};
export default EpgCard;
import styles from '../EpgCalendar.module.scss'
import type {Epg} from "../../../programmingGuide.types.ts";
import {useState} from "react";
import {Swiper, SwiperSlide} from "swiper/react";
import "swiper/swiper.css";
import EpgCard from "./epg-card";
import {FiChevronLeft, FiChevronRight} from "react-icons/fi";
import {useTheme} from "@wface/pixel-ui";
type Props = {
data: Epg[]
}
const EpgList = (props: Props) => {
const { data } = props;
const [swiper, setSwiper] = useState<any>(null);
const { expanded } = useTheme();
const handleNext = () => {
swiper.slideNext();
};
const handlePrev = () => {
swiper.slidePrev();
};
return (
<div className={`${styles.epgList} ${!expanded ? styles.larger : ''}`}>
<span className={styles.prev} onClick={handlePrev}>
<FiChevronLeft />
</span>
<Swiper
slidesPerView="auto"
onSwiper={setSwiper}
className={styles.swiper}
>
{
data?.map((epg) => {
return (
<SwiperSlide className={`${styles.sliderItem}`} key={epg.EpgId}>
<EpgCard epg={epg} />
</SwiperSlide>
);
})
}
</Swiper>
<span className={styles.next} onClick={handleNext}>
<FiChevronRight />
</span>
</div>
);
};
export default EpgList;
.epgCalendar {
.row {
display: flex;
align-items: stretch;
border-bottom: 1px solid var(--border-default);
}
.channel {
display: flex;
align-items: center;
padding: 20px 32px;
width: 250px;
border-right: 1px solid var(--border-default);
background: var(--elevation-surface-hovered);
.code {
display: flex;
align-items: center;
justify-content: center;
padding: 0 4px;
border-radius: 4px;
border: 1px solid var(--background-accent-gray-default);
height: 16px;
font-size: 12px;
line-height: 16px;
color: var(--text-subtle);
font-weight: 500;
}
.img {
padding-left: 16px;
margin-left: 16px;
position: relative;
&:after {
content: "";
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 1px;
height: 32px;
background: var(--border-default);
}
img {
max-height: 40px;
}
}
}
.epgList {
flex: 1;
max-width: calc(100vw - 490px);
position: relative;
.swiper {
.sliderItem {
width: 168px;
}
}
.prev, .next {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: var(--background-accent-gray-subtlest-default);
border-radius: 4px;
cursor: pointer;
z-index: 10;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: .3s;
&:hover {
background: var(--background-accent-purple-subtlest-default);
}
}
.prev {
left: -16px;
}
.next {
right: 16px;
}
&:hover {
.prev, .next {
opacity: 1;
visibility: visible;
pointer-events: all;
}
}
&.larger {
max-width: calc(100vw - 302px);
}
}
}
import styles from './EpgCalendar.module.scss'
import {useProgrammingGuide} from "../../programmingGuideProvider.tsx";
import Channel from "./channel";
import EpgList from "./epg-list";
const EpgCalendar = () => {
const { data } = useProgrammingGuide();
return (
<div className={styles.epgCalendar}>
{
data.map((item, index: number) => (
<div className={styles.row} key={index}>
<Channel data={item}/>
<EpgList data={item.Epgs}/>
</div>
))
}
</div>
);
};
export default EpgCalendar;
import {useMemo} from 'react';
import styles from "../EpgHeader.module.scss";
import {PiBody, PiHeading} from "@wface/pixel-ui";
import {getImageForCountry} from "../../../../../../helpers";
import {useProgrammingGuide} from "../../../programmingGuideProvider.tsx";
import type {Country} from "../../../programmingGuide.types.ts";
const EpgChannel = () => {
const { filter, countries } = useProgrammingGuide();
const country = useMemo<Country | undefined>(() => countries.find(i => i.ApplicationId === filter.applicationId), [countries, filter]);
return (
<div className={styles.epgChannel}>
{
country &&
(
<>
<div className={styles.country}>
<PiHeading variant="h4">{country?.ApplicationName}</PiHeading>
<span className={styles.flag}>
{getImageForCountry(Number(filter.applicationId), '24px', '24px')}
</span>
</div>
<div className={styles.applicationInfo}>
<PiBody size="sm" weight="regular">Timezone: <PiBody as="span" weight="light" size="sm">UTC +10</PiBody></PiBody>
<PiBody size="sm" weight="regular">Both linear & overflow.</PiBody>
</div>
</>
)
}
</div>
);
};
export default EpgChannel;
import styles from '../EpgHeader.module.scss'
import {PiBody, PiHeading, useTheme} from "@wface/pixel-ui";
import { Swiper, SwiperSlide } from "swiper/react";
import { useState} from "react";
import "swiper/swiper.css";
import {FiChevronLeft, FiChevronRight} from "react-icons/fi";
const generateTimeSlots = (start: number, end: number) => {
const times: { isActive: boolean, value: string }[] = [];
const currentHour = new Date().getHours();
for (let hour = start; hour <= end; hour++) {
const formattedHour = hour.toString().padStart(2, "0");
if (hour === currentHour) {
times.push({ isActive: true, value: `${formattedHour}:00` });
} else {
times.push({ isActive: false, value: `${formattedHour}:00` });
}
}
return times;
};
const EpgDate = () => {
const times = generateTimeSlots(0, 23);
const { expanded } = useTheme();
const [swiper, setSwiper] = useState<any>(null);
const handleNext = () => {
swiper.slideNext();
};
const handlePrev = () => {
swiper.slidePrev();
};
return (
<div className={`${styles.epgDate} ${!expanded ? styles.larger : ''}`}>
<div className={styles.date}>
<PiBody size="md" weight="light">TODAY</PiBody>
<PiHeading variant="h4">20 November 2025</PiHeading>
</div>
<div className={styles.hours}>
<span className={styles.prev} onClick={handlePrev}>
<FiChevronLeft />
</span>
<div className={styles.hourSlider}>
<Swiper
slidesPerView={14}
onSwiper={setSwiper}
className={styles.swiper}
>
{
times?.map((time) => {
return (
<SwiperSlide className={`${styles.sliderItem}`} key={time.value}>
<div className={`${styles.hour} ${time.isActive ? styles.active : ''}`}>
<PiBody size="sm" weight="light" as="span">{time.value}</PiBody>
</div>
</SwiperSlide>
);
})
}
</Swiper>
</div>
<span className={styles.next} onClick={handleNext}>
<FiChevronRight />
</span>
</div>
</div>
);
};
export default EpgDate;
.epgHeader {
display: flex;
align-items: stretch;
border-bottom: 1px solid var(--border-default);
}
.epgChannel {
width: 250px;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 22px 32px;
border-right: 1px solid var(--border-default);
.country {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 32px;
.flag {
padding-left: 20px;
position: relative;
display: flex;
align-items: center;
justify-content: center;
&:before {
content: "";
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
width: 1px;
height: 32px;
background: var(--border-default);
}
}
}
}
.epgDate {
width: 100%;
max-width: calc(100vw - 490px);
.date {
padding: 16px;
border-bottom: 1px solid var(--border-default);
}
.hours {
display: flex;
align-items: center;
padding: 12px;
width: 100%;
.hourSlider {
width: calc(100% - 64px);
.swiper {
width: 100%;
.sliderItem {
.hour {
cursor: pointer;
text-align: center;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
span {
color: var(--text-subtlest) !important;
}
&.active {
background: var(--elevation-surface-overlay);
border-radius: 4px;
border: 1px solid var(--border-default);
span {
font-weight: 400 !important;
color: var(--text-default) !important;
}
}
}
}
}
}
.prev, .next {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
background: var(--background-accent-gray-subtlest-default);
color: var(--icon-default);
cursor: pointer;
}
}
&.larger {
max-width: calc(100vw - 302px);
}
}
import styles from './EpgHeader.module.scss'
import EpgChannel from "./epg-channel";
import EpgDate from "./epg-date";
const EpgHeader = () => {
return (
<div className={styles.epgHeader}>
<EpgChannel />
<EpgDate />
</div>
);
};
export default EpgHeader;
const FilterModal = () => {
return (
<div>
FilterModal
</div>
);
};
export default FilterModal;
.filter {
.filterWrapper {
display: flex;
align-items: center;
gap: 10px;
.select {
min-width: 200px;
}
}
}
import styles from './Filter.module.scss'
import {useProgrammingGuide} from "../../programmingGuideProvider.tsx";
import {PiSelect, PiSkeleton} from "@wface/pixel-ui";
import {useMemo} from "react";
type Option = {
value: string | number
label: string
}
const Filter = () => {
const { filter, setFilter, countries, countryLoading } = useProgrammingGuide();
const countryOptions = useMemo<Option[]>(() => {
return countries.map(i => ({
label: i.ApplicationName,
value: i.ApplicationId,
}));
}, [countries]);
return (
<div className={styles.filter}>
<div className={styles.filterWrapper}>
{
countryLoading ? <PiSkeleton height={38} width={200} sx={{ transform: 'scale(1)' }} /> : (
<div className={styles.select}>
<PiSelect
options={countryOptions}
value={countryOptions?.find(i => i.value === filter.applicationId)}
onChange={(option: Option) => setFilter({...filter, applicationId: option.value})}
placeholder="Filter Country"
/>
</div>
)
}
{
countryLoading ? <PiSkeleton height={38} width={200} sx={{ transform: 'scale(1)' }} /> : (
<div className={styles.select}>
<PiSelect
options={[]}
placeholder="Filter Channels"
/>
</div>
)
}
</div>
</div>
);
};
export default Filter;
import {PiButton, PiModal} from "@wface/pixel-ui";
import DashboardLayout from "../../layouts/dashboard-layout";
import Filter from './components/filter'
import {FiCalendar} from "react-icons/fi";
import EpgHeader from "./components/epg-header";
import EpgCalendar from "./components/epg-calendar";
import styles from './ProgrammingGuide.module.scss';
import {ProgrammingGuideProvider} from "./programmingGuideProvider.tsx";
import React from "react";
import FilterModal from "./components/filter-modal";
const ProgrammingGuide = () => {
const [filterOpen, setFilterOpen] = React.useState(false);
const topbar = {
pageTitle: "Programming Guide",
breadcrumb: [],
primary:
<PiButton variant="secondary" prefixIcon={<FiCalendar />} onClick={() => setFilterOpen(true)}>
Go To
</PiButton>,
secondary: <PiButton variant="primary">Today</PiButton>
}
return (
<ProgrammingGuideProvider>
<DashboardLayout topbar={topbar} filterComponent={<Filter />} mainCn={styles.main}>
<EpgHeader />
<EpgCalendar />
</DashboardLayout>
<PiModal
open={filterOpen}
onClose={() => setFilterOpen(false)}
modalOptions={{
title: 'Filter Options',
subtitle: 'Adjust your filter settings',
size: 'md',
icon: <FiCalendar size={24} />
}}
>
<FilterModal />
</PiModal>
</ProgrammingGuideProvider>
);
};
export default ProgrammingGuide;

Sorry, the diff of this file is too big to display

export interface Country {
ApplicationId: number | string,
ApplicationName: string
}
export interface Filter {
applicationId: string | number | null
channelContentIds: number[]
rangeStartDate: string
rangeEndDate: string,
epgDate: string
}
export interface EpgData {
ContentId: number
CmsChannelId: string
Name: string
DisplayName: string
IsOverflow: boolean
DisplayChannelOrder: number
ChannelImagePath: string
Epgs: Epg[]
}
export interface Epg {
StartTimeUtc: string
EndTimeUtc: string
ProgramName: string
EventName: string
EpgId: string
EpgElementKey: string
IsLive: boolean
LiveToVod: boolean
EpgLock: boolean
HasPoster: boolean
ChannelId: string
DocumentId: string
Duration: number
OptaId?: number | null
EventId?: number | null
VideoTag?: string | null
LeagueId?: number | null
LeagueName?: string | null
HomeTeamId?: number | null
HomeTeamName?: string | null
VisitorTeamId?: number | null
VisitorTeamName?: string | null
PosterPath: any
EpgMetadataId: number
EpgKey: number
AvailableApplications: string[]
ApplicationIds: number[]
}
import React, {createContext, useContext, useEffect, useState} from "react";
import type {Country, EpgData, Filter} from "./programmingGuide.types.ts";
import {fetchCountries, fetchEpgData} from "./mockServer.ts";
interface Props {
children: React.ReactNode
}
interface ProgrammingGuideContextValue {
loading: boolean
filter: Filter,
setFilter: (value: Filter) => void
data: EpgData[],
countryLoading: boolean,
countries: Country[],
refresh: () => void;
}
const ProgrammingGuideContext = createContext<ProgrammingGuideContextValue | undefined>(undefined);
export const ProgrammingGuideProvider = (props: Props) => {
const { children } = props;
const [loading, setLoading] = useState<boolean>(false);
const [data, setData] = useState([]);
const [filter, setFilter] = useState<Filter>({
applicationId: null,
rangeStartDate: "",
rangeEndDate: "",
channelContentIds: [],
epgDate: ""
});
const [countryLoading, setCountryLoading] = useState<boolean>(false);
const [countries, setCountries] = useState<Country[]>([]);
const getCountries = async () => {
setCountryLoading(true);
try {
const res = await fetchCountries() as any;
setCountries(res);
}
catch (error) {
console.log(error)
setCountries([])
}
finally {
setCountryLoading(false)
}
}
useEffect(() => {
getCountries();
}, []);
const fetchData = async () => {
setLoading(true);
try {
const res = await fetchEpgData(filter) as any;
setData(res)
setLoading(false);
}
catch (error) {
console.log(error)
setData([]);
}
finally {
setLoading(false);
}
}
useEffect(() => {
if (countries.length) setFilter({...filter, applicationId: countries[0].ApplicationId});
}, [countries]);
useEffect(() => {
fetchData();
}, [filter]);
const value = {
loading,
filter,
setFilter,
data,
countryLoading,
countries,
refresh: fetchData
};
return (
<ProgrammingGuideContext.Provider value={value}>
{ children }
</ProgrammingGuideContext.Provider>
)
}
/* eslint-disable react-refresh/only-export-components */
export const useProgrammingGuide = (): ProgrammingGuideContextValue => {
const ctx = useContext(ProgrammingGuideContext);
if (!ctx) {
throw new Error("useProgrammingGuide must be used within ProgrammingGuideProvider");
}
return ctx;
}
+5
-5

@@ -102,3 +102,3 @@ #!/usr/bin/env node

function resolveTemplatePath(category, name) {
if (category === 'blocks' || category === 'layouts') {
if (category === 'blocks' || category === 'layouts' || category === 'views') {
return path.join(SRC_TEMPLATES, 'components', category, name)

@@ -144,3 +144,3 @@ }

let depDir = dir
if (depCat === 'blocks' || depCat === 'layouts') depDir = depCat
if (depCat === 'blocks' || depCat === 'layouts' || depCat === 'views') depDir = depCat

@@ -234,3 +234,3 @@ await addTemplateRecursive({

const program = new Command()
program.name('pixel-ui').description('Pixel UI CLI').version('0.2.0')
program.name('pixel-ui').description('Pixel UI CLI').version('0.3.0')

@@ -260,3 +260,3 @@ program

.command('add')
.argument('<category>', 'blocks | layouts | wface | components')
.argument('<category>', 'blocks | layouts | views')
.argument('<name>', 'template name')

@@ -277,5 +277,5 @@ .option('--base <path>', 'base components directory', 'src/components')

.description('List available component templates')
.option('--category <name>', 'filter by subcategory (e.g. blocks, layouts)')
.option('--category <name>', 'filter by subcategory (e.g. blocks, layouts, views)')
.action(({ category }) => listTemplates(category))
program.parse(process.argv)
{
"name": "@wface/pixel-cli",
"version": "0.3.0",
"version": "0.3.1",
"type": "module",

@@ -19,9 +19,2 @@ "bin": {

},
"dependencies": {
"commander": "^12.1.0",
"fs-extra": "^11.2.0",
"kleur": "^4.1.5",
"open": "^10.2.0",
"serve-handler": "^6.1.6"
},
"engines": {

@@ -34,4 +27,10 @@ "node": ">=18"

"devDependencies": {
"@types/serve-handler": "^6"
}
"@types/serve-handler": "^6",
"commander": "^12.1.0",
"fs-extra": "^11.2.0",
"kleur": "^4.1.5",
"open": "^10.2.0",
"serve-handler": "^6.1.6"
},
"packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72"
}

@@ -6,2 +6,5 @@ .sidebar {

border-right: 1px solid var(--border-default);
position: sticky;
top: 65px;
z-index: 2;

@@ -182,2 +185,5 @@ .sidebarWrapper {

}
&:last-child {
border-bottom: none;
}
}

@@ -184,0 +190,0 @@ }

.topbar {
border-bottom: 2px solid var(--border-default);
border-bottom: 1px solid var(--border-default);
background: var(--elevation-surface);
position: sticky;
top: 65px;
z-index: 2;
.topbarWrapper {

@@ -4,0 +9,0 @@ display: flex;

import styles from './Topnav.module.scss'
import settings from "../../../constants/settings.ts";
import settings from "../../../constants/settings";
import {FiLogOut, FiMoon, FiSun} from "react-icons/fi";

@@ -4,0 +4,0 @@ import {PiAvatar, useTheme} from "@wface/pixel-ui";

.topnav {
border-bottom: 1px solid var(--border-default);
background: var(--elevation-surface);
position: sticky;
top: 0;
z-index: 2;
.topnavWrapper {

@@ -4,0 +9,0 @@ display: flex;

{
"dependencies": [
{ "category": "blocks", "name": "sidebar"},
{ "category": "blocks", "name": "topbar"},
{ "category": "blocks", "name": "topnav"}
{ "category": "blocks", "name": "topnav"},
{ "category": "blocks", "name": "login-form"}
]
}

@@ -13,6 +13,7 @@ import styles from './DashboardLayout.module.scss';

className?: string;
mainCn?: string
}
const DashboardLayout = (props: Props) => {
const { children, className, ...rest } = props;
const { children, className, mainCn, ...rest } = props;

@@ -31,3 +32,3 @@ const auth = {

<Topbar {...rest} />
<div className={styles.main}>
<div className={`${mainCn ? mainCn : ''} ${styles.main}`}>
{ children }

@@ -34,0 +35,0 @@ </div>

{
"dependencies": [
{ "category": "blocks", "name": "sidebar"},
{ "category": "blocks", "name": "topbar"}
{ "category": "blocks", "name": "topbar"},
{ "category": "blocks", "name": "topnav"}
]
}