Merged in chore/delete-unused-files (pull request #3346)
chore: Delete unused files * Delete unused files Ignore design-system for now Approved-by: Joakim Jäderberg
This commit is contained in:
@@ -1,6 +0,0 @@
|
||||
import { useBookingFlowContext } from "@scandic-hotels/booking-flow/hooks/useBookingFlowContext"
|
||||
|
||||
export function useIsUserLoggedIn() {
|
||||
const { isLoggedIn } = useBookingFlowContext()
|
||||
return isLoggedIn
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
import { Suspense } from "react"
|
||||
|
||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
import { TrackingSDK } from "@scandic-hotels/tracking/TrackingSDK"
|
||||
|
||||
import Blocks from "@/components/Blocks"
|
||||
import HeaderDynamicContent from "@/components/Headers/DynamicContent"
|
||||
import Hero from "@/components/Hero"
|
||||
import { HeroVideo } from "@/components/HeroVideo"
|
||||
import MeetingPackageWidget from "@/components/MeetingPackageWidget"
|
||||
import Sidebar from "@/components/Sidebar"
|
||||
import SidebarSkeleton from "@/components/Sidebar/SidebarSkeleton"
|
||||
import LinkChips from "@/components/TempDesignSystem/LinkChips"
|
||||
|
||||
import { staticPageVariants } from "./variants"
|
||||
|
||||
import styles from "./staticPage.module.css"
|
||||
|
||||
import type { StaticPageProps } from "./staticPage"
|
||||
|
||||
export default async function StaticPage({
|
||||
content,
|
||||
tracking,
|
||||
pageType,
|
||||
}: StaticPageProps) {
|
||||
const { blocks, hero_image, hero_video, header, meeting_package } = content
|
||||
|
||||
return (
|
||||
<>
|
||||
<section className={staticPageVariants({ pageType })}>
|
||||
<header className={styles.header}>
|
||||
<div className={styles.headerContent}>
|
||||
{header ? (
|
||||
<>
|
||||
<div className={styles.headerIntro}>
|
||||
<Typography variant="Title/lg">
|
||||
<h1 className={styles.heading}>{header.heading}</h1>
|
||||
</Typography>
|
||||
<Typography variant="Body/Lead text">
|
||||
<p>{header.preamble}</p>
|
||||
</Typography>
|
||||
</div>
|
||||
{header.top_primary_button?.url ? (
|
||||
<ButtonLink
|
||||
href={header.top_primary_button.url}
|
||||
size="Medium"
|
||||
variant="Tertiary"
|
||||
className={styles.button}
|
||||
>
|
||||
{header.top_primary_button.title}
|
||||
</ButtonLink>
|
||||
) : null}
|
||||
{header.navigation_links ? (
|
||||
<LinkChips chips={header.navigation_links} />
|
||||
) : null}
|
||||
{"dynamic_content" in header &&
|
||||
header.dynamic_content !== null &&
|
||||
header.dynamic_content !== undefined ? (
|
||||
<HeaderDynamicContent {...header.dynamic_content} />
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{hero_video || hero_image ? (
|
||||
<div className={styles.heroWrapper}>
|
||||
{hero_video ? (
|
||||
<HeroVideo
|
||||
className={styles.heroVideo}
|
||||
src={hero_video.src}
|
||||
focalPoint={hero_video.focalPoint}
|
||||
captions={hero_video.captions}
|
||||
/>
|
||||
) : null}
|
||||
{!hero_video && hero_image ? (
|
||||
<Hero
|
||||
className={styles.heroImage}
|
||||
alt={hero_image.meta.alt || hero_image.meta.caption || ""}
|
||||
src={hero_image.url}
|
||||
focalPoint={hero_image.focalPoint}
|
||||
dimensions={hero_image.dimensions}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className={styles.contentContainer}>
|
||||
<main className={styles.mainContent}>
|
||||
{pageType === "collection" && meeting_package?.show_widget && (
|
||||
<MeetingPackageWidget
|
||||
destination={meeting_package.location}
|
||||
className={styles.meetingPackageWidget}
|
||||
/>
|
||||
)}
|
||||
{blocks ? <Blocks blocks={blocks} /> : null}
|
||||
</main>
|
||||
|
||||
{"sidebar" in content && content.sidebar?.length ? (
|
||||
<Suspense fallback={<SidebarSkeleton />}>
|
||||
<Sidebar blocks={content.sidebar} />
|
||||
</Suspense>
|
||||
) : null}
|
||||
</div>
|
||||
</section>
|
||||
<TrackingSDK pageData={tracking} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
.page {
|
||||
padding-bottom: var(--Space-x9);
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: var(--Base-Surface-Subtle-Normal);
|
||||
padding-bottom: var(--Space-x4);
|
||||
}
|
||||
|
||||
.headerContent {
|
||||
display: grid;
|
||||
gap: var(--Space-x3);
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.headerIntro {
|
||||
display: grid;
|
||||
max-width: var(--max-width-text-block);
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--Text-Heading);
|
||||
text-wrap: balance;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.heroWrapper {
|
||||
padding: var(--Space-x4) var(--Space-x2);
|
||||
}
|
||||
|
||||
.heroImage,
|
||||
.heroVideo {
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.contentContainer {
|
||||
width: 100%;
|
||||
padding: var(--Space-x4) var(--Space-x2) 0;
|
||||
}
|
||||
|
||||
.content .contentContainer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"main"
|
||||
"sidebar";
|
||||
gap: var(--Space-x4);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: var(--Space-x6);
|
||||
margin: 0 auto;
|
||||
max-width: var(--max-width-content);
|
||||
}
|
||||
|
||||
.content .mainContent {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
.meetingPackageWidget {
|
||||
border-radius: var(--Corner-radius-lg);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button {
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.contentContainer {
|
||||
padding: var(--Space-x4) 0;
|
||||
max-width: var(--max-width-content);
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.heroContainer {
|
||||
padding: var(--Space-x4) 0;
|
||||
}
|
||||
|
||||
.headerIntro {
|
||||
gap: var(--Space-x3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Meeting booking widget changes design at 948px */
|
||||
@media screen and (min-width: 948px) {
|
||||
.meetingPackageWidget {
|
||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||
box-shadow: 0px 4px 24px 0px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1367px) {
|
||||
.content .contentContainer {
|
||||
grid-template-areas: "main sidebar";
|
||||
grid-template-columns: var(--max-width-text-block) 1fr;
|
||||
gap: var(--Space-x9);
|
||||
}
|
||||
|
||||
.mainContent {
|
||||
gap: var(--Space-x9);
|
||||
padding: 0;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { TrackingSDKPageData } from "@scandic-hotels/tracking/types"
|
||||
import type { CollectionPage } from "@scandic-hotels/trpc/types/collectionPage"
|
||||
import type { ContentPage } from "@scandic-hotels/trpc/types/contentPage"
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
import type { staticPageVariants } from "./variants"
|
||||
|
||||
export interface StaticPageProps
|
||||
extends Omit<React.HTMLAttributes<HTMLDivElement>, "content">,
|
||||
VariantProps<typeof staticPageVariants> {
|
||||
pageType?: "collection" | "content"
|
||||
content: CollectionPage["collection_page"] | ContentPage["content_page"]
|
||||
tracking: TrackingSDKPageData
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { cva } from "class-variance-authority"
|
||||
|
||||
import styles from "./staticPage.module.css"
|
||||
|
||||
export const staticPageVariants = cva(styles.page, {
|
||||
variants: {
|
||||
pageType: {
|
||||
collection: styles.collection,
|
||||
content: styles.content,
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
pageType: "content",
|
||||
},
|
||||
})
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { FocalPoint } from "@scandic-hotels/common/utils/imageVault"
|
||||
import type { Image } from "@scandic-hotels/trpc/types/image"
|
||||
|
||||
export interface HeroProps {
|
||||
alt: string
|
||||
src: string
|
||||
focalPoint?: FocalPoint
|
||||
dimensions?: Image["dimension"]
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import Link from "@scandic-hotels/design-system/OldDSLink"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { getMembershipCards } from "@/lib/trpc/memoizedRequests"
|
||||
|
||||
import { getIntl } from "@/i18n"
|
||||
|
||||
import styles from "./membershipcards.module.css"
|
||||
|
||||
export default async function MembershipCardSlot() {
|
||||
const intl = await getIntl()
|
||||
const membershipCards = await getMembershipCards()
|
||||
|
||||
return (
|
||||
<section className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h3>
|
||||
{intl.formatMessage({
|
||||
id: "myPages.myMembershipCards",
|
||||
defaultMessage: "My membership cards",
|
||||
})}
|
||||
</h3>
|
||||
</Typography>
|
||||
</div>
|
||||
{(membershipCards || []).map((card, idx) => (
|
||||
<div className={styles.card} key={idx}>
|
||||
<Typography variant="Title/Subtitle/md">
|
||||
<h4 className={styles.subTitle}>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "myPages.nameWithCardMembershipType",
|
||||
defaultMessage: "Name: {cardMembershipType}",
|
||||
},
|
||||
{
|
||||
cardMembershipType: card.membershipType,
|
||||
}
|
||||
)}
|
||||
</h4>
|
||||
</Typography>
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "myPages.currentPointsWithPoints",
|
||||
defaultMessage: "Current Points: {points, number}",
|
||||
},
|
||||
{ points: card.currentPoints }
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "myPages.memberSinceWithValue",
|
||||
defaultMessage: "Member Since: {value}",
|
||||
},
|
||||
{
|
||||
value: card.memberSince,
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
<span data-hj-suppress>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "myPages.numberWithValue",
|
||||
defaultMessage: "Number: {membershipNumber}",
|
||||
},
|
||||
{
|
||||
membershipNumber: card.membershipNumber,
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
<span>
|
||||
{intl.formatMessage(
|
||||
{
|
||||
id: "myPages.expirationDateWithDate",
|
||||
defaultMessage: "Expiration Date: {expirationDate}",
|
||||
},
|
||||
{
|
||||
expirationDate: card.expirationDate?.split("T")[0],
|
||||
}
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
<Link href="#" variant="icon">
|
||||
<MaterialIcon icon="add_circle" color="CurrentColor" />
|
||||
<Typography
|
||||
variant="Body/Paragraph/mdRegular"
|
||||
className={styles.addNewCardText}
|
||||
>
|
||||
<p>
|
||||
{intl.formatMessage({
|
||||
id: "myPages.addNewCard",
|
||||
defaultMessage: "Add new card",
|
||||
})}
|
||||
</p>
|
||||
</Typography>
|
||||
</Link>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
.container {
|
||||
display: grid;
|
||||
gap: var(--Space-x3);
|
||||
max-width: 510px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: grid;
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-top: var(--Space-x4);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: repeat(3, auto);
|
||||
gap: var(--Space-x1);
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.addNewCardText {
|
||||
text-decoration: underline;
|
||||
color: var(--Scandic-Brand-Burgundy);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { VariantProps } from "class-variance-authority"
|
||||
|
||||
import type { linkVariants } from "./variants"
|
||||
|
||||
export interface SectionLinkProps
|
||||
extends React.PropsWithChildren<React.HTMLAttributes<HTMLSpanElement>>,
|
||||
VariantProps<typeof linkVariants> {
|
||||
link?: { href: string; text: string }
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { ApiImage } from "@scandic-hotels/trpc/types/hotel"
|
||||
|
||||
export type PreviewImagesProps = {
|
||||
images: ApiImage[]
|
||||
hotelName: string
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import type { User } from "@scandic-hotels/trpc/types/user"
|
||||
|
||||
export interface FriendProps
|
||||
extends React.PropsWithChildren<Pick<User, "membership" | "name">> {}
|
||||
@@ -1,5 +0,0 @@
|
||||
export type PointsColumnProps = {
|
||||
title: string
|
||||
subtitle?: string
|
||||
value?: number | null
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
import { useReactToPrint } from "react-to-print"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { OldDSButton as Button } from "@scandic-hotels/design-system/OldDSButton"
|
||||
|
||||
import type { DownloadInvoiceProps } from "../../../../types/components/bookingConfirmation/actions/downloadInvoice"
|
||||
|
||||
export default function DownloadInvoice({ mainRef }: DownloadInvoiceProps) {
|
||||
const intl = useIntl()
|
||||
const reactToPrintFn = useReactToPrint({ contentRef: mainRef })
|
||||
|
||||
function downloadBooking() {
|
||||
reactToPrintFn()
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
intent="text"
|
||||
onPress={downloadBooking}
|
||||
size="small"
|
||||
theme="base"
|
||||
variant="icon"
|
||||
wrapping
|
||||
>
|
||||
<MaterialIcon icon="download" color="CurrentColor" />
|
||||
{intl.formatMessage({
|
||||
id: "bookingConfirmation.downloadInvoice",
|
||||
defaultMessage: "Download invoice",
|
||||
})}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import type { MutableRefObject } from "react"
|
||||
|
||||
export interface DownloadInvoiceProps {
|
||||
mainRef: MutableRefObject<HTMLElement | null>
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import type { CurrencyEnum } from "@scandic-hotels/common/constants/currency"
|
||||
import type { RoomPackageCodeEnum } from "@scandic-hotels/trpc/enums/roomFilter"
|
||||
import type { Room } from "@scandic-hotels/trpc/types/hotel"
|
||||
import type { Package, PackageEnum } from "@scandic-hotels/trpc/types/packages"
|
||||
import type {
|
||||
Product,
|
||||
RoomConfiguration,
|
||||
RoomsAvailability,
|
||||
} from "@scandic-hotels/trpc/types/roomAvailability"
|
||||
|
||||
import type { BookingCodeFilterEnum } from "../../stores/bookingCode-filter"
|
||||
import type {
|
||||
Rate,
|
||||
Room as RoomBooking,
|
||||
SelectRateBooking,
|
||||
} from "../components/selectRate/selectRate"
|
||||
|
||||
export interface AvailabilityError {
|
||||
details: string
|
||||
error: string
|
||||
}
|
||||
|
||||
interface Actions {
|
||||
appendRegularRates: (
|
||||
roomConfigurations: RoomConfiguration[] | undefined
|
||||
) => void
|
||||
closeSection: () => void
|
||||
modifyRate: () => void
|
||||
removeSelectedPackage: (code: PackageEnum) => void
|
||||
removeSelectedPackages: () => void
|
||||
selectFilter: (filter: BookingCodeFilterEnum) => void
|
||||
selectPackages: (codes: PackageEnum[]) => void
|
||||
selectRate: (rate: SelectedRate, isUserLoggedIn: boolean) => void
|
||||
updateRooms: (rooms: RoomConfiguration[] | undefined) => void
|
||||
}
|
||||
|
||||
export interface SelectedRate {
|
||||
features: RoomConfiguration["features"]
|
||||
product: Product
|
||||
roomType: RoomConfiguration["roomType"]
|
||||
roomTypeCode: RoomConfiguration["roomTypeCode"]
|
||||
}
|
||||
|
||||
export interface SelectedRoom {
|
||||
actions: Actions
|
||||
bookingRoom: RoomBooking
|
||||
isFetchingAdditionalRate: boolean
|
||||
isFetchingPackages: boolean
|
||||
rooms: RoomConfiguration[]
|
||||
selectedFilter: BookingCodeFilterEnum | undefined
|
||||
selectedPackages: Package[]
|
||||
selectedRate: SelectedRate | null
|
||||
}
|
||||
|
||||
interface DefaultFilterOptions {
|
||||
code: RoomPackageCodeEnum
|
||||
description: string
|
||||
}
|
||||
|
||||
export interface RatesState {
|
||||
activeRoom: number
|
||||
booking: SelectRateBooking
|
||||
hotelType: string | undefined
|
||||
isRedemptionBooking: boolean
|
||||
packageOptions: DefaultFilterOptions[]
|
||||
rateSummary: Array<Rate | null>
|
||||
rooms: SelectedRoom[]
|
||||
roomCategories: Room[]
|
||||
roomConfigurations: RoomConfiguration[][]
|
||||
roomsPackages: Package[][]
|
||||
roomsAvailability: (RoomsAvailability | AvailabilityError)[] | undefined
|
||||
vat: number
|
||||
defaultCurrency: CurrencyEnum
|
||||
}
|
||||
|
||||
export interface InitialState
|
||||
extends Pick<
|
||||
RatesState,
|
||||
"booking" | "hotelType" | "roomCategories" | "roomsAvailability" | "vat"
|
||||
> {
|
||||
initialActiveRoom?: number
|
||||
pathname: string
|
||||
labels: {
|
||||
accessibilityRoom: string
|
||||
allergyRoom: string
|
||||
petRoom: string
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { Contact } from "../Contact.graphql"
|
||||
|
||||
export const ContactAside = gql`
|
||||
fragment ContactAside on CurrentBlocksPageAsideContact {
|
||||
contact {
|
||||
contactConnection {
|
||||
edges {
|
||||
node {
|
||||
...Contact
|
||||
}
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
${Contact}
|
||||
`
|
||||
@@ -1,12 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { Puff } from "../Puff.graphql"
|
||||
|
||||
export const PuffAside = gql`
|
||||
fragment PuffAside on CurrentBlocksPageAsidePuff {
|
||||
puff {
|
||||
...Puff
|
||||
}
|
||||
}
|
||||
${Puff}
|
||||
`
|
||||
@@ -1,39 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const ListItem = gql`
|
||||
fragment ListItem on CurrentBlocksPageBlocksListBlockListItemsListItem {
|
||||
list_item {
|
||||
list_item_style
|
||||
subtitle
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ListItemExternalLink = gql`
|
||||
fragment ListItemExternalLink on CurrentBlocksPageBlocksListBlockListItemsListItemExternalLink {
|
||||
list_item_external_link {
|
||||
link {
|
||||
href
|
||||
title
|
||||
}
|
||||
list_item_style
|
||||
subtitle
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ListBlock = gql`
|
||||
fragment ListBlock on CurrentBlocksPageBlocksList {
|
||||
list {
|
||||
list_items {
|
||||
__typename
|
||||
...ListItem
|
||||
...ListItemExternalLink
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
${ListItem}
|
||||
${ListItemExternalLink}
|
||||
`
|
||||
@@ -1,18 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { Puff } from "../Puff.graphql"
|
||||
|
||||
export const PuffBlock = gql`
|
||||
fragment PuffBlock on CurrentBlocksPageBlocksPuffs {
|
||||
puffs {
|
||||
puffs {
|
||||
... on CurrentBlocksPageBlocksPuffsBlockPuffsPuff {
|
||||
puff {
|
||||
...Puff
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${Puff}
|
||||
`
|
||||
@@ -1,23 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "../SysAsset.graphql"
|
||||
|
||||
export const TextBlock = gql`
|
||||
fragment TextBlock on CurrentBlocksPageBlocksText {
|
||||
text {
|
||||
content {
|
||||
embedded_itemsConnection {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const CurrentBlocksPageBreadcrumbs = gql`
|
||||
fragment CurrentBlocksPageBreadcrumbs on CurrentBlocksPage {
|
||||
breadcrumbs {
|
||||
parents {
|
||||
href
|
||||
title
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,32 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { System } from "../System.graphql"
|
||||
|
||||
export const PromoCampaignPageBreadcrumb = gql`
|
||||
fragment PromoCampaignPageBreadcrumb on PromoCampaignPage {
|
||||
web {
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
url
|
||||
}
|
||||
${System}
|
||||
`
|
||||
|
||||
export const PromoCampaignPageBreadcrumbRef = gql`
|
||||
fragment PromoCampaignPageBreadcrumbRef on PromoCampaignPage {
|
||||
web {
|
||||
breadcrumbs {
|
||||
title
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${System}
|
||||
`
|
||||
@@ -1,71 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const ContactExtraInfo = gql`
|
||||
fragment ContactExtraInfo on ContactBlockSectionsExtraInfo {
|
||||
extra_info {
|
||||
text
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ContactMailingAddress = gql`
|
||||
fragment ContactMailingAddress on ContactBlockSectionsMailingAddress {
|
||||
mailing_address {
|
||||
city
|
||||
country
|
||||
name
|
||||
street
|
||||
zip
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ContactPhone = gql`
|
||||
fragment ContactPhone on ContactBlockSectionsPhone {
|
||||
phone {
|
||||
number
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ContactTitle = gql`
|
||||
fragment ContactTitle on ContactBlockSectionsTitle {
|
||||
title {
|
||||
text
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const ContactVisitingAddress = gql`
|
||||
fragment ContactVisitingAddress on ContactBlockSectionsVisitingAddress {
|
||||
visiting_address {
|
||||
city
|
||||
country
|
||||
street
|
||||
zip
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const Contact = gql`
|
||||
fragment Contact on ContactBlock {
|
||||
sections {
|
||||
__typename
|
||||
...ContactExtraInfo
|
||||
...ContactMailingAddress
|
||||
...ContactPhone
|
||||
...ContactTitle
|
||||
...ContactVisitingAddress
|
||||
}
|
||||
system {
|
||||
locale
|
||||
uid
|
||||
}
|
||||
}
|
||||
${ContactExtraInfo}
|
||||
${ContactMailingAddress}
|
||||
${ContactPhone}
|
||||
${ContactTitle}
|
||||
${ContactVisitingAddress}
|
||||
`
|
||||
@@ -1,32 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "../SysAsset.graphql"
|
||||
|
||||
export const CurrentFooterAppDownloads = gql`
|
||||
fragment CurrentFooterAppDownloads on CurrentFooter {
|
||||
app_downloads {
|
||||
title
|
||||
app_store {
|
||||
href
|
||||
imageConnection {
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
google_play {
|
||||
href
|
||||
imageConnection {
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,16 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "../SysAsset.graphql"
|
||||
|
||||
export const Logo = gql`
|
||||
fragment Logo on CurrentFooter {
|
||||
logoConnection {
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,33 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const MainLinks = gql`
|
||||
fragment MainLinks on Footer {
|
||||
main_links {
|
||||
title
|
||||
open_in_new_tab
|
||||
link {
|
||||
href
|
||||
title
|
||||
}
|
||||
pageConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
... on AccountPage {
|
||||
title
|
||||
url
|
||||
}
|
||||
... on LoyaltyPage {
|
||||
title
|
||||
url
|
||||
}
|
||||
... on ContentPage {
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,13 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const Navigation = gql`
|
||||
fragment Navigation on CurrentFooter {
|
||||
navigation {
|
||||
links {
|
||||
href
|
||||
title
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,31 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { AccountPageRef } from "../../AccountPage/Ref.graphql"
|
||||
import { ContentPageRef } from "../../ContentPage/Ref.graphql"
|
||||
import { LoyaltyPageRef } from "../../LoyaltyPage/Ref.graphql"
|
||||
import { System } from "../../System.graphql"
|
||||
|
||||
export const MainLinksRef = gql`
|
||||
fragment MainLinksRef on Footer {
|
||||
__typename
|
||||
main_links {
|
||||
pageConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageRef
|
||||
...ContentPageRef
|
||||
...AccountPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${System}
|
||||
${LoyaltyPageRef}
|
||||
${AccountPageRef}
|
||||
${ContentPageRef}
|
||||
`
|
||||
@@ -1,33 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { AccountPageRef } from "../../AccountPage/Ref.graphql"
|
||||
import { ContentPageRef } from "../../ContentPage/Ref.graphql"
|
||||
import { LoyaltyPageRef } from "../../LoyaltyPage/Ref.graphql"
|
||||
import { System } from "../../System.graphql"
|
||||
|
||||
export const SecondaryLinksRef = gql`
|
||||
fragment SecondaryLinksRef on Footer {
|
||||
__typename
|
||||
secondary_links {
|
||||
links {
|
||||
pageConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageRef
|
||||
...ContentPageRef
|
||||
...AccountPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${System}
|
||||
${LoyaltyPageRef}
|
||||
${AccountPageRef}
|
||||
${ContentPageRef}
|
||||
`
|
||||
@@ -1,24 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const SecondaryLinks = gql`
|
||||
fragment SecondaryLinks on Footer {
|
||||
secondary_links {
|
||||
title
|
||||
links {
|
||||
title
|
||||
open_in_new_tab
|
||||
pageConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
}
|
||||
link {
|
||||
href
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,21 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const CurrentFooterSocialMedia = gql`
|
||||
fragment CurrentFooterSocialMedia on CurrentFooter {
|
||||
social_media {
|
||||
title
|
||||
facebook {
|
||||
href
|
||||
title
|
||||
}
|
||||
instagram {
|
||||
href
|
||||
title
|
||||
}
|
||||
twitter {
|
||||
href
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,19 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "../SysAsset.graphql"
|
||||
|
||||
export const TripAdvisor = gql`
|
||||
fragment TripAdvisor on CurrentFooter {
|
||||
trip_advisor {
|
||||
logoConnection {
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
title
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,29 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { AccountPageRef } from "../../AccountPage/Ref.graphql"
|
||||
import { ContentPageRef } from "../../ContentPage/Ref.graphql"
|
||||
import { LoyaltyPageRef } from "../../LoyaltyPage/Ref.graphql"
|
||||
|
||||
export const TertiaryLinksRef = gql`
|
||||
fragment TertiaryLinksRef on Footer {
|
||||
__typename
|
||||
tertiary_links {
|
||||
pageConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...LoyaltyPageRef
|
||||
...ContentPageRef
|
||||
...AccountPageRef
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${LoyaltyPageRef}
|
||||
${AccountPageRef}
|
||||
${ContentPageRef}
|
||||
`
|
||||
@@ -1,21 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const Grid = gql`
|
||||
fragment Grid on Grid {
|
||||
columns {
|
||||
span
|
||||
rows {
|
||||
rowConnection {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
... on Card {
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,17 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "./SysAsset.graphql"
|
||||
|
||||
export const Hero = gql`
|
||||
fragment Hero on Hero {
|
||||
imagesConnection {
|
||||
totalCount
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,14 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { System } from "../System.graphql"
|
||||
|
||||
export const CurrentBlocksPageLink = gql`
|
||||
fragment CurrentBlocksPageLink on CurrentBlocksPage {
|
||||
title
|
||||
url
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${System}
|
||||
`
|
||||
@@ -1,14 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { System } from "../System.graphql"
|
||||
|
||||
export const CurrentBlocksPageLink = gql`
|
||||
fragment CurrentBlocksPageLink on CurrentBlocksPage {
|
||||
title
|
||||
url
|
||||
system {
|
||||
...System
|
||||
}
|
||||
}
|
||||
${System}
|
||||
`
|
||||
@@ -1,22 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "./SysAsset.graphql"
|
||||
|
||||
export const Preamble = gql`
|
||||
fragment Preamble on CurrentBlocksPage {
|
||||
preamble {
|
||||
text {
|
||||
json
|
||||
embedded_itemsConnection(limit: 30) {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,25 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
import { SysAsset } from "./SysAsset.graphql"
|
||||
|
||||
export const Puff = gql`
|
||||
fragment Puff on Puff {
|
||||
imageConnection {
|
||||
edges {
|
||||
node {
|
||||
...SysAsset
|
||||
}
|
||||
}
|
||||
}
|
||||
puff_style
|
||||
link {
|
||||
href
|
||||
title
|
||||
}
|
||||
text {
|
||||
json
|
||||
}
|
||||
title
|
||||
}
|
||||
${SysAsset}
|
||||
`
|
||||
@@ -1,19 +0,0 @@
|
||||
import { gql } from "graphql-tag"
|
||||
|
||||
export const GetRewards = gql`
|
||||
query GetRewards($locale: String!, $rewardIds: [String!]) {
|
||||
all_reward(locale: $locale, where: { reward_id_in: $rewardIds }) {
|
||||
items {
|
||||
taxonomies {
|
||||
term_uid
|
||||
}
|
||||
label
|
||||
grouped_label
|
||||
description
|
||||
grouped_description
|
||||
value
|
||||
reward_id
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
@@ -1,150 +0,0 @@
|
||||
# GraphQL to TypeScript Converter
|
||||
|
||||
This script converts GraphQL files (`.graphql`) to TypeScript files (`.graphql.ts`) that use the `gql` template literal from `graphql-tag`.
|
||||
|
||||
## Features
|
||||
|
||||
- Converts individual fragments and queries into separate TypeScript exports
|
||||
- Handles GraphQL imports (`#import`) and converts them to TypeScript imports
|
||||
- Preserves fragment references and generates proper import statements
|
||||
- Groups multiple imports from the same file
|
||||
- Supports fragments, queries, mutations, and subscriptions
|
||||
- Handles files with multiple operations
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Conversion
|
||||
|
||||
Convert all GraphQL files in the project:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert
|
||||
```
|
||||
|
||||
Convert files matching a specific pattern:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "packages/trpc/**/*.graphql"
|
||||
```
|
||||
|
||||
Convert a single file:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "path/to/file.graphql"
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
- `--dry-run`: Preview what files would be converted without actually converting them
|
||||
- `--delete-originals`: Delete original `.graphql` files after successful conversion
|
||||
- `--help`, `-h`: Show help message
|
||||
|
||||
### Examples
|
||||
|
||||
Preview conversion:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert --dry-run
|
||||
```
|
||||
|
||||
Convert and delete originals:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert --delete-originals
|
||||
```
|
||||
|
||||
Convert specific directory:
|
||||
|
||||
```bash
|
||||
yarn graphql:convert "packages/trpc/lib/graphql/Fragments/**/*.graphql"
|
||||
```
|
||||
|
||||
## Input Format
|
||||
|
||||
### GraphQL Fragment
|
||||
|
||||
```graphql
|
||||
fragment Contact on ContactBlock {
|
||||
sections {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GraphQL Query with Imports
|
||||
|
||||
```graphql
|
||||
#import "../Fragments/System.graphql"
|
||||
#import "../Fragments/Metadata.graphql"
|
||||
|
||||
query GetData($locale: String!) {
|
||||
data(locale: $locale) {
|
||||
...System
|
||||
...Metadata
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Output Format
|
||||
|
||||
### TypeScript Fragment
|
||||
|
||||
```typescript
|
||||
import { gql } from "graphql-tag";
|
||||
|
||||
export const Contact = gql`
|
||||
fragment Contact on ContactBlock {
|
||||
sections {
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
```
|
||||
|
||||
### TypeScript Query with Imports
|
||||
|
||||
```typescript
|
||||
import { gql } from "graphql-tag";
|
||||
|
||||
import { System } from "../Fragments/System.graphql";
|
||||
import { Metadata } from "../Fragments/Metadata.graphql";
|
||||
|
||||
export const GetData = gql`
|
||||
query GetData($locale: String!) {
|
||||
data(locale: $locale) {
|
||||
...System
|
||||
...Metadata
|
||||
}
|
||||
}
|
||||
${System}
|
||||
${Metadata}
|
||||
`;
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Parse GraphQL Files**: Reads `.graphql` files and extracts imports and operations
|
||||
2. **Handle Imports**: Converts `#import` statements to TypeScript imports by:
|
||||
- Reading the imported file to determine export names
|
||||
- Converting paths from `.graphql` to `.graphql.ts`
|
||||
- Grouping multiple imports from the same file
|
||||
3. **Extract Operations**: Identifies fragments, queries, mutations, and subscriptions
|
||||
4. **Generate TypeScript**: Creates TypeScript files with:
|
||||
- `gql` template literals
|
||||
- Proper import statements
|
||||
- Named exports for each operation
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `glob`: For file pattern matching
|
||||
- `tsx`: For TypeScript execution
|
||||
- `@types/node`: For Node.js types
|
||||
- `graphql-tag`: For the `gql` template literal (runtime dependency)
|
||||
|
||||
## Notes
|
||||
|
||||
- The script preserves the original file structure and naming
|
||||
- Fragment references (`...FragmentName`) are preserved in the GraphQL content
|
||||
- Multiple operations in a single file are split into separate exports
|
||||
- Import conflicts are avoided by using the exact export names from referenced files
|
||||
- Generated files maintain the same directory structure with `.graphql.ts` extension
|
||||
@@ -1,373 +0,0 @@
|
||||
#!/usr/bin/env tsx
|
||||
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { glob } from "glob";
|
||||
|
||||
interface ImportInfo {
|
||||
fragmentName: string;
|
||||
importPath: string;
|
||||
variableName: string;
|
||||
}
|
||||
|
||||
interface GraphQLFile {
|
||||
content: string;
|
||||
imports: ImportInfo[];
|
||||
operations: Array<{ name: string; content: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts individual fragments/operations from GraphQL content
|
||||
*/
|
||||
function extractOperations(
|
||||
content: string
|
||||
): Array<{ name: string; content: string }> {
|
||||
const operations: Array<{ name: string; content: string }> = [];
|
||||
|
||||
// Split content into lines for processing
|
||||
const lines = content.split("\n");
|
||||
let currentOperation: { name: string; content: string[] } | null = null;
|
||||
let braceCount = 0;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// Check if this line starts a new operation
|
||||
const fragmentMatch = trimmedLine.match(/^fragment\s+(\w+)\s+on/);
|
||||
const queryMatch = trimmedLine.match(/^query\s+(\w+)\s*[({]/);
|
||||
const mutationMatch = trimmedLine.match(/^mutation\s+(\w+)\s*[({]/);
|
||||
const subscriptionMatch = trimmedLine.match(
|
||||
/^subscription\s+(\w+)\s*[({]/
|
||||
);
|
||||
|
||||
if (fragmentMatch || queryMatch || mutationMatch || subscriptionMatch) {
|
||||
// Finish previous operation if exists
|
||||
if (currentOperation && currentOperation.content.length > 0) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
// Start new operation
|
||||
const operationName = (fragmentMatch ||
|
||||
queryMatch ||
|
||||
mutationMatch ||
|
||||
subscriptionMatch)![1];
|
||||
currentOperation = {
|
||||
name: operationName,
|
||||
content: [line],
|
||||
};
|
||||
|
||||
// Count braces in the current line
|
||||
braceCount =
|
||||
(line.match(/{/g) || []).length -
|
||||
(line.match(/}/g) || []).length;
|
||||
} else if (currentOperation) {
|
||||
// Add line to current operation
|
||||
currentOperation.content.push(line);
|
||||
|
||||
// Update brace count
|
||||
braceCount +=
|
||||
(line.match(/{/g) || []).length -
|
||||
(line.match(/}/g) || []).length;
|
||||
|
||||
// If we've closed all braces, this operation is complete
|
||||
if (braceCount === 0 && trimmedLine.includes("}")) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
currentOperation = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where file ends without closing brace
|
||||
if (currentOperation && currentOperation.content.length > 0) {
|
||||
operations.push({
|
||||
name: currentOperation.name,
|
||||
content: currentOperation.content.join("\n").trim(),
|
||||
});
|
||||
}
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates TypeScript content from parsed GraphQL
|
||||
*/
|
||||
function generateTypeScriptContent(parsedFile: GraphQLFile): string {
|
||||
const { imports, operations } = parsedFile;
|
||||
|
||||
let output = 'import { gql } from "graphql-tag"\n';
|
||||
|
||||
// Add imports for fragments - group by import path to avoid duplicates
|
||||
if (imports.length > 0) {
|
||||
output += "\n";
|
||||
|
||||
const importsByPath = new Map<string, string[]>();
|
||||
for (const imp of imports) {
|
||||
if (!importsByPath.has(imp.importPath)) {
|
||||
importsByPath.set(imp.importPath, []);
|
||||
}
|
||||
if (
|
||||
!importsByPath.get(imp.importPath)!.includes(imp.variableName)
|
||||
) {
|
||||
importsByPath.get(imp.importPath)!.push(imp.variableName);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [importPath, variableNames] of importsByPath) {
|
||||
output += `import { ${variableNames.join(", ")} } from "${importPath}"\n`;
|
||||
}
|
||||
}
|
||||
|
||||
output += "\n";
|
||||
|
||||
// Generate exports for each operation
|
||||
if (operations.length === 0) {
|
||||
// If no operation names found, use a default export
|
||||
const defaultName = "GraphQLDocument";
|
||||
const fragmentSubstitutions =
|
||||
imports.length > 0
|
||||
? "\n" +
|
||||
imports.map((imp) => `\${${imp.variableName}}`).join("\n")
|
||||
: "";
|
||||
output += `export const ${defaultName} = gql\`\n${parsedFile.content}${fragmentSubstitutions}\n\`\n`;
|
||||
} else {
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const operation = operations[i];
|
||||
const fragmentSubstitutions =
|
||||
imports.length > 0
|
||||
? "\n" +
|
||||
imports.map((imp) => `\${${imp.variableName}}`).join("\n")
|
||||
: "";
|
||||
output += `export const ${operation.name} = gql\`\n${operation.content}${fragmentSubstitutions}\n\`\n`;
|
||||
|
||||
if (i < operations.length - 1) {
|
||||
output += "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a GraphQL import path to a TypeScript import path
|
||||
*/
|
||||
function convertImportPath(graphqlPath: string): string {
|
||||
// Remove the .graphql extension and add .graphql
|
||||
const withoutExt = graphqlPath.replace(/\.graphql$/, "");
|
||||
return `${withoutExt}.graphql`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the export names from a GraphQL file by analyzing the fragments it contains
|
||||
*/
|
||||
function getExportNamesFromGraphQLFile(filePath: string): string[] {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const operations = extractOperations(content);
|
||||
return operations.map((op) => op.name);
|
||||
} catch {
|
||||
// If file doesn't exist or can't be read, try to infer from path
|
||||
console.warn(
|
||||
`Warning: Could not read ${filePath}, inferring export name from path`
|
||||
);
|
||||
return [getVariableNameFromPath(filePath)];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the expected variable name from a file path
|
||||
*/
|
||||
function getVariableNameFromPath(filePath: string): string {
|
||||
const basename = path.basename(filePath, ".graphql");
|
||||
|
||||
// Convert kebab-case or snake_case to PascalCase
|
||||
return basename
|
||||
.split(/[-_]/)
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a GraphQL file and extracts imports and content
|
||||
*/
|
||||
function parseGraphQLFile(filePath: string): GraphQLFile {
|
||||
const content = fs.readFileSync(filePath, "utf-8");
|
||||
const lines = content.split("\n");
|
||||
const imports: ImportInfo[] = [];
|
||||
const contentLines: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
if (trimmedLine.startsWith("#import")) {
|
||||
// Extract import path from #import "path"
|
||||
const match = trimmedLine.match(/#import\s+"([^"]+)"/);
|
||||
if (match) {
|
||||
const importPath = match[1];
|
||||
const fullImportPath = path.resolve(
|
||||
path.dirname(filePath),
|
||||
importPath
|
||||
);
|
||||
const exportNames =
|
||||
getExportNamesFromGraphQLFile(fullImportPath);
|
||||
const tsImportPath = convertImportPath(importPath);
|
||||
|
||||
// Add all exports from the imported file
|
||||
for (const exportName of exportNames) {
|
||||
imports.push({
|
||||
fragmentName: exportName,
|
||||
importPath: tsImportPath,
|
||||
variableName: exportName,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (trimmedLine && !trimmedLine.startsWith("#")) {
|
||||
contentLines.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
const cleanContent = contentLines.join("\n").trim();
|
||||
const operations = extractOperations(cleanContent);
|
||||
|
||||
return {
|
||||
content: cleanContent,
|
||||
imports,
|
||||
operations,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a single GraphQL file to TypeScript
|
||||
*/
|
||||
function convertFile(graphqlPath: string): void {
|
||||
try {
|
||||
console.log(`Converting: ${graphqlPath}`);
|
||||
|
||||
const parsed = parseGraphQLFile(graphqlPath);
|
||||
const tsContent = generateTypeScriptContent(parsed);
|
||||
const tsPath = graphqlPath.replace(/\.graphql$/, ".graphql.ts");
|
||||
|
||||
fs.writeFileSync(tsPath, tsContent, "utf-8");
|
||||
console.log(`✓ Created: ${tsPath}`);
|
||||
|
||||
// Optionally remove the original .graphql file
|
||||
// Uncomment the next line if you want to delete the original files
|
||||
// fs.unlinkSync(graphqlPath)
|
||||
} catch (error) {
|
||||
console.error(`Error converting ${graphqlPath}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to convert all GraphQL files
|
||||
*/
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
console.log(`
|
||||
GraphQL to TypeScript Converter
|
||||
|
||||
Converts GraphQL files (.graphql) to TypeScript files (.graphql.ts) using gql template literals.
|
||||
|
||||
Usage: tsx convert-graphql-to-ts.ts [options] [pattern]
|
||||
|
||||
Options:
|
||||
--help, -h Show this help message
|
||||
--dry-run Show what files would be converted without converting them
|
||||
--delete-originals Delete original .graphql files after conversion
|
||||
|
||||
Examples:
|
||||
tsx convert-graphql-to-ts.ts # Convert all .graphql files
|
||||
tsx convert-graphql-to-ts.ts "packages/trpc/**/*.graphql" # Convert specific pattern
|
||||
tsx convert-graphql-to-ts.ts --dry-run # Preview conversion
|
||||
tsx convert-graphql-to-ts.ts --delete-originals # Convert and delete originals
|
||||
|
||||
Features:
|
||||
• Converts fragments, queries, mutations, and subscriptions
|
||||
• Handles GraphQL imports (#import) and converts to TypeScript imports
|
||||
• Preserves fragment references and generates proper import statements
|
||||
• Groups multiple imports from the same file
|
||||
• Splits multiple operations into separate exports
|
||||
`);
|
||||
return;
|
||||
}
|
||||
|
||||
const dryRun = args.includes("--dry-run");
|
||||
const deleteOriginals = args.includes("--delete-originals");
|
||||
|
||||
// Get the pattern from args or use default
|
||||
const pattern = args.find((arg) => !arg.startsWith("--")) || "**/*.graphql";
|
||||
|
||||
console.log(`🔍 Searching for GraphQL files with pattern: ${pattern}`);
|
||||
|
||||
try {
|
||||
const files = await glob(pattern, {
|
||||
ignore: ["**/*.graphql.ts", "**/node_modules/**"],
|
||||
});
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log("❌ No GraphQL files found.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📁 Found ${files.length} GraphQL files`);
|
||||
|
||||
if (dryRun) {
|
||||
console.log("\n📋 Files that would be converted:");
|
||||
files.forEach((file, index) => {
|
||||
console.log(
|
||||
` ${index + 1}. ${file} → ${file.replace(/\.graphql$/, ".graphql.ts")}`
|
||||
);
|
||||
});
|
||||
console.log("\n🔍 --dry-run mode: No files were converted.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\n🔄 Converting files...");
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
try {
|
||||
console.log(
|
||||
`📝 [${i + 1}/${files.length}] Converting: ${file}`
|
||||
);
|
||||
convertFile(file);
|
||||
successCount++;
|
||||
|
||||
if (deleteOriginals) {
|
||||
fs.unlinkSync(file);
|
||||
console.log(`🗑️ Deleted: ${file}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error converting ${file}:`, error);
|
||||
errorCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\n✅ Conversion complete!`);
|
||||
console.log(` 📈 Successfully converted: ${successCount} files`);
|
||||
if (errorCount > 0) {
|
||||
console.log(` ❌ Errors: ${errorCount} files`);
|
||||
}
|
||||
if (deleteOriginals && successCount > 0) {
|
||||
console.log(` 🗑️ Deleted: ${successCount} original files`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("❌ Error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main().catch(console.error);
|
||||
}
|
||||
Reference in New Issue
Block a user