@driveflux/pdf 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.eslintrc +3 -0
  2. package/.swcrc +14 -0
  3. package/.turbo/turbo-build.log +6 -0
  4. package/CHANGELOG.md +69 -0
  5. package/dist/__mocks__/subscription.d.ts +3 -0
  6. package/dist/__mocks__/subscription.d.ts.map +1 -0
  7. package/dist/__mocks__/subscription.js +1069 -0
  8. package/dist/cjs/__mocks__/subscription.js +1079 -0
  9. package/dist/cjs/components/FluxLogo.js +76 -0
  10. package/dist/cjs/components/StandardPage.js +129 -0
  11. package/dist/cjs/debug.js +202 -0
  12. package/dist/cjs/index.js +20 -0
  13. package/dist/cjs/package.json +3 -0
  14. package/dist/cjs/templates/SubscriptionAgreement/Confirmation.js +95 -0
  15. package/dist/cjs/templates/SubscriptionAgreement/CoverPage.js +110 -0
  16. package/dist/cjs/templates/SubscriptionAgreement/CoverPageSection.js +35 -0
  17. package/dist/cjs/templates/SubscriptionAgreement/Details.js +161 -0
  18. package/dist/cjs/templates/SubscriptionAgreement/Footer.js +68 -0
  19. package/dist/cjs/templates/SubscriptionAgreement/Header.js +61 -0
  20. package/dist/cjs/templates/SubscriptionAgreement/LabelValue.js +39 -0
  21. package/dist/cjs/templates/SubscriptionAgreement/SubscriptionAgreementPage.js +48 -0
  22. package/dist/cjs/templates/SubscriptionAgreement/TermsOfUse.js +72 -0
  23. package/dist/cjs/templates/SubscriptionAgreement/TermsOfUseSubSection.js +108 -0
  24. package/dist/cjs/templates/SubscriptionAgreement/index.js +276 -0
  25. package/dist/cjs/templates/SubscriptionAgreement/translations.js +666 -0
  26. package/dist/cjs/templates/SubscriptionAgreement/utils.js +14 -0
  27. package/dist/cjs/templates/index.js +18 -0
  28. package/dist/cjs/theme/colors.js +147 -0
  29. package/dist/cjs/theme/index.js +18 -0
  30. package/dist/cjs/types.js +4 -0
  31. package/dist/cjs/utils.js +165 -0
  32. package/dist/cjs/watch.js +20 -0
  33. package/dist/components/FluxLogo.d.ts +3 -0
  34. package/dist/components/FluxLogo.d.ts.map +1 -0
  35. package/dist/components/FluxLogo.js +66 -0
  36. package/dist/components/StandardPage.d.ts +6 -0
  37. package/dist/components/StandardPage.d.ts.map +1 -0
  38. package/dist/components/StandardPage.js +114 -0
  39. package/dist/debug.d.ts +2 -0
  40. package/dist/debug.d.ts.map +1 -0
  41. package/dist/debug.js +198 -0
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +3 -0
  45. package/dist/templates/SubscriptionAgreement/Confirmation.d.ts +10 -0
  46. package/dist/templates/SubscriptionAgreement/Confirmation.d.ts.map +1 -0
  47. package/dist/templates/SubscriptionAgreement/Confirmation.js +80 -0
  48. package/dist/templates/SubscriptionAgreement/CoverPage.d.ts +12 -0
  49. package/dist/templates/SubscriptionAgreement/CoverPage.d.ts.map +1 -0
  50. package/dist/templates/SubscriptionAgreement/CoverPage.js +95 -0
  51. package/dist/templates/SubscriptionAgreement/CoverPageSection.d.ts +7 -0
  52. package/dist/templates/SubscriptionAgreement/CoverPageSection.d.ts.map +1 -0
  53. package/dist/templates/SubscriptionAgreement/CoverPageSection.js +25 -0
  54. package/dist/templates/SubscriptionAgreement/Details.d.ts +10 -0
  55. package/dist/templates/SubscriptionAgreement/Details.d.ts.map +1 -0
  56. package/dist/templates/SubscriptionAgreement/Details.js +146 -0
  57. package/dist/templates/SubscriptionAgreement/Footer.d.ts +10 -0
  58. package/dist/templates/SubscriptionAgreement/Footer.d.ts.map +1 -0
  59. package/dist/templates/SubscriptionAgreement/Footer.js +58 -0
  60. package/dist/templates/SubscriptionAgreement/Header.d.ts +10 -0
  61. package/dist/templates/SubscriptionAgreement/Header.d.ts.map +1 -0
  62. package/dist/templates/SubscriptionAgreement/Header.js +46 -0
  63. package/dist/templates/SubscriptionAgreement/LabelValue.d.ts +8 -0
  64. package/dist/templates/SubscriptionAgreement/LabelValue.d.ts.map +1 -0
  65. package/dist/templates/SubscriptionAgreement/LabelValue.js +29 -0
  66. package/dist/templates/SubscriptionAgreement/SubscriptionAgreementPage.d.ts +10 -0
  67. package/dist/templates/SubscriptionAgreement/SubscriptionAgreementPage.d.ts.map +1 -0
  68. package/dist/templates/SubscriptionAgreement/SubscriptionAgreementPage.js +33 -0
  69. package/dist/templates/SubscriptionAgreement/TermsOfUse.d.ts +8 -0
  70. package/dist/templates/SubscriptionAgreement/TermsOfUse.d.ts.map +1 -0
  71. package/dist/templates/SubscriptionAgreement/TermsOfUse.js +57 -0
  72. package/dist/templates/SubscriptionAgreement/TermsOfUseSubSection.d.ts +12 -0
  73. package/dist/templates/SubscriptionAgreement/TermsOfUseSubSection.d.ts.map +1 -0
  74. package/dist/templates/SubscriptionAgreement/TermsOfUseSubSection.js +98 -0
  75. package/dist/templates/SubscriptionAgreement/index.d.ts +14 -0
  76. package/dist/templates/SubscriptionAgreement/index.d.ts.map +1 -0
  77. package/dist/templates/SubscriptionAgreement/index.js +261 -0
  78. package/dist/templates/SubscriptionAgreement/translations.d.ts +72 -0
  79. package/dist/templates/SubscriptionAgreement/translations.d.ts.map +1 -0
  80. package/dist/templates/SubscriptionAgreement/translations.js +656 -0
  81. package/dist/templates/SubscriptionAgreement/utils.d.ts +3 -0
  82. package/dist/templates/SubscriptionAgreement/utils.d.ts.map +1 -0
  83. package/dist/templates/SubscriptionAgreement/utils.js +4 -0
  84. package/dist/templates/index.d.ts +2 -0
  85. package/dist/templates/index.d.ts.map +1 -0
  86. package/dist/templates/index.js +1 -0
  87. package/dist/theme/colors.d.ts +148 -0
  88. package/dist/theme/colors.d.ts.map +1 -0
  89. package/dist/theme/colors.js +137 -0
  90. package/dist/theme/index.d.ts +2 -0
  91. package/dist/theme/index.d.ts.map +1 -0
  92. package/dist/theme/index.js +1 -0
  93. package/dist/types.d.ts +2 -0
  94. package/dist/types.d.ts.map +1 -0
  95. package/dist/types.js +1 -0
  96. package/dist/utils.d.ts +4 -0
  97. package/dist/utils.d.ts.map +1 -0
  98. package/dist/utils.js +150 -0
  99. package/dist/watch.d.ts +2 -0
  100. package/dist/watch.d.ts.map +1 -0
  101. package/dist/watch.js +11 -0
  102. package/index.cjs +1 -0
  103. package/index.d.ts +1 -0
  104. package/package.json +43 -0
  105. package/src/__mocks__/subscription.ts +803 -0
  106. package/src/components/FluxLogo.tsx +52 -0
  107. package/src/components/StandardPage.tsx +32 -0
  108. package/src/debug.ts +36 -0
  109. package/src/index.ts +3 -0
  110. package/src/templates/SubscriptionAgreement/Confirmation.tsx +49 -0
  111. package/src/templates/SubscriptionAgreement/CoverPage.tsx +52 -0
  112. package/src/templates/SubscriptionAgreement/CoverPageSection.tsx +25 -0
  113. package/src/templates/SubscriptionAgreement/Details.tsx +127 -0
  114. package/src/templates/SubscriptionAgreement/Footer.tsx +37 -0
  115. package/src/templates/SubscriptionAgreement/Header.tsx +37 -0
  116. package/src/templates/SubscriptionAgreement/LabelValue.tsx +19 -0
  117. package/src/templates/SubscriptionAgreement/SubscriptionAgreementPage.tsx +24 -0
  118. package/src/templates/SubscriptionAgreement/TermsOfUse.tsx +30 -0
  119. package/src/templates/SubscriptionAgreement/TermsOfUseSubSection.tsx +44 -0
  120. package/src/templates/SubscriptionAgreement/index.tsx +66 -0
  121. package/src/templates/SubscriptionAgreement/translations.ts +664 -0
  122. package/src/templates/SubscriptionAgreement/utils.ts +7 -0
  123. package/src/templates/index.ts +1 -0
  124. package/src/theme/colors.ts +180 -0
  125. package/src/theme/index.ts +1 -0
  126. package/src/types.ts +1 -0
  127. package/src/utils.ts +10 -0
  128. package/src/watch.ts +16 -0
  129. package/tsconfig.json +13 -0
  130. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,52 @@
1
+ import { Defs, G, Path, Rect, Svg } from '@react-pdf/renderer'
2
+
3
+ const FluxLogo = () => {
4
+ return (
5
+ <Svg width="92" height="29" viewBox="0 0 92 29" fill="none">
6
+ <G clipPath="url(#clip0_664_102105)">
7
+ <Path
8
+ d="M23.6621 6.12305H35.6903V8.00999H25.5568V13.5529H33.805V15.434H25.5568V22.8638H23.6715L23.6621 6.12305Z"
9
+ fill="black"
10
+ />
11
+ <Path d="M40.9981 6.12305H42.8834V20.9827H52.3039V22.8697H40.9922L40.9981 6.12305Z" fill="black" />
12
+ <Path
13
+ d="M57.6172 15.6002V6.12305H59.5025V15.4811C59.5025 19.0946 61.4149 21.129 64.6187 21.129C67.7035 21.129 69.6642 19.2621 69.6642 15.6002V6.12305H71.5495V15.4552C71.5495 20.3376 68.751 22.8744 64.5669 22.8744C60.4381 22.8756 57.6172 20.3388 57.6172 15.6002Z"
14
+ fill="black"
15
+ />
16
+ <Path
17
+ d="M83.2965 14.3324L77.1033 6.12305H79.3338L84.4736 12.9632L89.5663 6.12305H91.7674L85.5742 14.3077L92.0054 22.8756H89.7525L84.4006 15.6722L79.0451 22.8756H76.8594L83.2965 14.3324Z"
18
+ fill="black"
19
+ />
20
+ <Path
21
+ opacity="0.3"
22
+ d="M3.05934 17.5614C2.45475 17.5598 1.86415 17.3792 1.36192 17.0423C0.859683 16.7054 0.468271 16.2273 0.23697 15.6682C0.00566877 15.1091 -0.0551799 14.494 0.0620863 13.9004C0.179353 13.3068 0.469491 12.7611 0.895964 12.3322L12.3256 0.892604C12.909 0.364801 13.6728 0.0817665 14.459 0.102098C15.2452 0.12243 15.9934 0.444573 16.5488 1.00183C17.1042 1.55908 17.4243 2.30877 17.4426 3.09567C17.461 3.88257 17.1764 4.64641 16.6476 5.22904L5.218 16.6686C4.93441 16.9521 4.59781 17.1768 4.22742 17.33C3.85703 17.4832 3.46011 17.5618 3.05934 17.5614Z"
23
+ fill="#50C8E8"
24
+ />
25
+ <Path
26
+ opacity="0.3"
27
+ d="M8.77458 23.28C8.16964 23.2788 7.57859 23.0985 7.07593 22.7616C6.57326 22.4248 6.18149 21.9465 5.94999 21.3871C5.7185 20.8277 5.65764 20.2123 5.7751 19.6183C5.89256 19.0244 6.18308 18.4786 6.61002 18.0496L12.3213 12.3334C12.6004 12.027 12.9387 11.7804 13.3158 11.6086C13.6928 11.4368 14.1008 11.3433 14.5149 11.3338C14.9291 11.3243 15.3409 11.399 15.7254 11.5533C16.1099 11.7076 16.4592 11.9384 16.752 12.2317C17.0449 12.525 17.2753 12.8746 17.4293 13.2596C17.5832 13.6445 17.6576 14.0567 17.6479 14.4712C17.6382 14.8858 17.5446 15.294 17.3727 15.6713C17.2008 16.0486 16.9543 16.3871 16.648 16.6663L10.9368 22.3837C10.653 22.6681 10.3159 22.8936 9.94489 23.0474C9.57386 23.2013 9.17617 23.2803 8.77458 23.28Z"
28
+ fill="#50C8E8"
29
+ />
30
+ <Path
31
+ d="M14.4866 29.0001C16.176 29.0001 17.5455 27.6294 17.5455 25.9385C17.5455 24.2477 16.176 22.877 14.4866 22.877C12.7972 22.877 11.4277 24.2477 11.4277 25.9385C11.4277 27.6294 12.7972 29.0001 14.4866 29.0001Z"
32
+ fill="#50C8E8"
33
+ />
34
+ <Path
35
+ d="M14.4866 6.12314C16.176 6.12314 17.5455 4.75243 17.5455 3.06157C17.5455 1.37071 16.176 0 14.4866 0C12.7972 0 11.4277 1.37071 11.4277 3.06157C11.4277 4.75243 12.7972 6.12314 14.4866 6.12314Z"
36
+ fill="#50C8E8"
37
+ />
38
+ <Path
39
+ d="M14.4866 17.5616C16.176 17.5616 17.5455 16.1909 17.5455 14.5C17.5455 12.8092 16.176 11.4385 14.4866 11.4385C12.7972 11.4385 11.4277 12.8092 11.4277 14.5C11.4277 16.1909 12.7972 17.5616 14.4866 17.5616Z"
40
+ fill="#50C8E8"
41
+ />
42
+ </G>
43
+ <Defs>
44
+ <clipPath id="clip0_664_102105">
45
+ <Rect width="92" height="29" fill="white" />
46
+ </clipPath>
47
+ </Defs>
48
+ </Svg>
49
+ )
50
+ }
51
+
52
+ export default FluxLogo
@@ -0,0 +1,32 @@
1
+ import { Font, Page, PageProps } from '@react-pdf/renderer'
2
+ import React, { PropsWithChildren } from 'react'
3
+
4
+ type Props = PageProps & {}
5
+
6
+ Font.register({
7
+ family: 'Figtree',
8
+ fonts: [
9
+ {
10
+ src: 'https://fonts.gstatic.com/s/figtree/v5/_Xmz-HUzqDCFdgfMsYiV_F7wfS-Bs_chQF5ewkEU4HTy.ttf',
11
+ fontWeight: 'normal'
12
+ },
13
+ {
14
+ src: 'https://fonts.gstatic.com/s/figtree/v5/_Xmz-HUzqDCFdgfMsYiV_F7wfS-Bs_eYR15ewkEU4HTy.ttf',
15
+ fontWeight: 'bold'
16
+ }
17
+ ]
18
+ })
19
+ Font.register({
20
+ family: 'Allura',
21
+ src: 'https://fonts.gstatic.com/s/allura/v21/9oRPNYsQpS4zjuAPjAIXPtrrGA.ttf'
22
+ })
23
+
24
+ const StandardDocument: React.FC<PropsWithChildren<Props>> = ({ children, style, ...props }) => {
25
+ return (
26
+ <Page style={{ fontFamily: 'Figtree', fontStyle: 'normal', ...style }} {...props}>
27
+ {children}
28
+ </Page>
29
+ )
30
+ }
31
+
32
+ export default StandardDocument
package/src/debug.ts ADDED
@@ -0,0 +1,36 @@
1
+ import { loadAllEnv } from '@driveflux/env'
2
+ import { subscription } from './__mocks__/subscription.js'
3
+ import { generateSubscriptionAgreementPdf } from './templates/SubscriptionAgreement/index.js'
4
+
5
+ const debugSubscriptionAgreement = async () => {
6
+ if (!subscription) {
7
+ console.error('no such subscription')
8
+ return
9
+ }
10
+
11
+ await generateSubscriptionAgreementPdf({ subscription }, `${process.cwd()}/output/subscription-agreement.pdf`)
12
+ }
13
+
14
+ //! put all the debugging functions here
15
+ const debugCases: Promise<any>[] = [
16
+ debugSubscriptionAgreement()
17
+ ]
18
+
19
+ const debug = async () => {
20
+ loadAllEnv()
21
+
22
+ const results = await Promise.allSettled(debugCases)
23
+
24
+ if (results.some((res) => res.status === 'rejected')) {
25
+ results.forEach((res, i) => {
26
+ if (res.status === 'rejected') {
27
+ console.error(`debug no ${i + 1} error:`, res.reason)
28
+ }
29
+ })
30
+ return
31
+ }
32
+
33
+ console.log('Generated all PDFs successfully!')
34
+ console.log(`You can view them in the 'output' folder!`)
35
+ }
36
+ debug()
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './templates/index.js'
2
+ export * from './types.js'
3
+ export * from './utils.js'
@@ -0,0 +1,49 @@
1
+ import { userName } from '@driveflux/db/models/user'
2
+ import { SubscriptionWithUser, SubscriptionWithVehicle } from '@driveflux/db/types'
3
+ import { Image, Text, View } from '@react-pdf/renderer'
4
+ import { format } from 'date-fns'
5
+ import { FC } from 'react'
6
+ import { colors } from '../../theme/index.js'
7
+ import LabelValue from './LabelValue.js'
8
+ import { SubscriptionAgreementTranslation } from './translations.js'
9
+
10
+ type Props = {
11
+ tConfirmation: SubscriptionAgreementTranslation['confirmation']
12
+ subscription: SubscriptionWithUser & SubscriptionWithVehicle
13
+ }
14
+
15
+ const Confirmation: FC<Props> = ({ tConfirmation, subscription }) => {
16
+ if (!subscription.signature?.signature) {
17
+ throw new Error('Missing signature')
18
+ }
19
+
20
+ const border = `1px solid ${colors.primary2}`
21
+
22
+ return (
23
+ <View style={{ borderLeft: border, borderRight: border, borderBottom: border }}>
24
+ <View style={{ backgroundColor: colors.primary1, borderTop: border, padding: 8 }}>
25
+ <Text style={{ fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: 1 }}>{tConfirmation.title}</Text>
26
+ </View>
27
+ <View style={{ padding: 8, paddingBottom: 16, rowGap: 8, borderTop: border }}>
28
+ <Text>{tConfirmation.agreementText(subscription.businessId ? 'business' : 'member')}</Text>
29
+ <View style={{ height: 160, alignItems: 'flex-start' }}>
30
+ <Image src={subscription.signature.signature} />
31
+ </View>
32
+ <View style={{ rowGap: 8 }}>
33
+ <LabelValue label={tConfirmation.name} value={userName(subscription.user)} />
34
+ <LabelValue label={tConfirmation.idOrPassportNo} value={subscription.user.identification?.number} />
35
+ <LabelValue
36
+ label={tConfirmation.date}
37
+ value={
38
+ subscription.signature.signedAt
39
+ ? format(subscription.signature.signedAt, 'dd-MM-yyyy hh:mm a')
40
+ : undefined
41
+ }
42
+ />
43
+ </View>
44
+ </View>
45
+ </View>
46
+ )
47
+ }
48
+
49
+ export default Confirmation
@@ -0,0 +1,52 @@
1
+ import { User } from '@driveflux/db'
2
+ import { userName } from '@driveflux/db/models/user'
3
+ import { Text, View } from '@react-pdf/renderer'
4
+ import { format } from 'date-fns'
5
+ import { FC } from 'react'
6
+ import { colors } from '../../theme/colors.js'
7
+ import CoverPageSection from './CoverPageSection.js'
8
+ import LabelValue from './LabelValue.js'
9
+ import { SubscriptionAgreementTranslation } from './translations.js'
10
+
11
+ type Props = {
12
+ tTitle: SubscriptionAgreementTranslation['title']
13
+ tCover: SubscriptionAgreementTranslation['cover']
14
+ signedAt: Date
15
+ user: User
16
+ }
17
+
18
+ const CoverPage: FC<Props> = ({ tTitle, tCover, signedAt, user }) => {
19
+ return (
20
+ <View style={{ flex: 1, paddingVertical: 80, justifyContent: 'space-between' }}>
21
+ <Text style={{ fontWeight: 'bold', fontSize: 40, textTransform: 'uppercase', flex: 1 }}>{tTitle}</Text>
22
+ <View style={{ padding: 16, rowGap: 32, border: `1px solid ${colors.primary2}`, fontSize: 10 }}>
23
+ <CoverPageSection title={tCover.effectiveDate}>
24
+ <View style={{ flexDirection: 'row', columnGap: 4 }}>
25
+ <Text style={{ textDecoration: 'underline' }}>{format(signedAt, 'do')}</Text>
26
+ <Text>{tCover.of}</Text>
27
+ <Text style={{ textDecoration: 'underline' }}>{format(signedAt, 'MMM')}</Text>
28
+ <Text>{format(signedAt, 'yyyy')}</Text>
29
+ </View>
30
+ </CoverPageSection>
31
+ <CoverPageSection title={tCover.agreementBetween}>
32
+ <LabelValue label={tCover.name} value={userName(user)} />
33
+ <LabelValue label={tCover.idOrPassportNo} value={user.identification?.number} />
34
+ </CoverPageSection>
35
+ <Text
36
+ style={{
37
+ fontSize: 8,
38
+ fontWeight: 'bold',
39
+ color: colors.primary3,
40
+ textTransform: 'uppercase',
41
+ letterSpacing: 1
42
+ }}
43
+ >
44
+ {tCover.and}
45
+ </Text>
46
+ <Text>FLUX Sdn Bhd (1264766-H)</Text>
47
+ </View>
48
+ </View>
49
+ )
50
+ }
51
+
52
+ export default CoverPage
@@ -0,0 +1,25 @@
1
+ import { Text, View } from '@react-pdf/renderer'
2
+ import { FC, PropsWithChildren } from 'react'
3
+ import { colors } from '../../theme/colors.js'
4
+
5
+ type Props = {
6
+ title: string
7
+ }
8
+ const CoverPageSection: FC<PropsWithChildren<Props>> = ({ title, children }) => {
9
+ return (
10
+ <View style={{ rowGap: 16 }}>
11
+ <Text
12
+ style={{
13
+ fontSize: 8,
14
+ fontWeight: 'bold',
15
+ color: colors.primary3,
16
+ textTransform: 'uppercase',
17
+ letterSpacing: 1
18
+ }}
19
+ >{`${title}:`}</Text>
20
+ {children}
21
+ </View>
22
+ )
23
+ }
24
+
25
+ export default CoverPageSection
@@ -0,0 +1,127 @@
1
+ import { MileagePackage, PlanType, VehicleType } from '@driveflux/db'
2
+ import { userName } from '@driveflux/db/models/user'
3
+ import { format } from '@driveflux/time'
4
+ import { Text, View } from '@react-pdf/renderer'
5
+ import { FC, useMemo } from 'react'
6
+ import { colors } from '../../theme/index.js'
7
+ import LabelValue from './LabelValue.js'
8
+ import { SubscriptionForAgreement } from './index.js'
9
+ import { SubscriptionAgreementTranslation } from './translations.js'
10
+
11
+ type Props = {
12
+ tDetails: SubscriptionAgreementTranslation['details']
13
+ subscription: SubscriptionForAgreement
14
+ }
15
+
16
+ const Details: FC<Props> = ({ tDetails, subscription }) => {
17
+ const details = useMemo(
18
+ () => ({
19
+ member: [
20
+ {
21
+ key: tDetails.member.name,
22
+ value: userName(subscription.user)
23
+ },
24
+ {
25
+ key: tDetails.member.idOrPassportNo,
26
+ value: subscription.user.identification?.number
27
+ }
28
+ ],
29
+ vehicle: [
30
+ {
31
+ key: tDetails.vehicle.make,
32
+ value: subscription.vehicle?.make
33
+ },
34
+ {
35
+ key: tDetails.vehicle.model,
36
+ value: subscription.vehicle?.vehicleModel
37
+ },
38
+ {
39
+ key: tDetails.vehicle.year,
40
+ value: subscription.vehicle?.year
41
+ },
42
+ {
43
+ key: tDetails.vehicle.plateNo,
44
+ value: subscription.vehicle?.registrationNumber
45
+ }
46
+ ],
47
+ subscription: [
48
+ {
49
+ key: tDetails.subscription.subscriptionPlan,
50
+ value: getPlanTranslation(subscription.plan)
51
+ },
52
+ {
53
+ key: tDetails.subscription.mileagePackage,
54
+ value: getFullMileagePackageTranslation(subscription.mileagePackage)
55
+ },
56
+ {
57
+ key: tDetails.subscription.startDate,
58
+ value: subscription.startedAt ? format(subscription.startedAt, 'dd-MM-yyyy') : undefined
59
+ },
60
+ {
61
+ key: tDetails.subscription.endDate,
62
+ value: subscription.endedAt
63
+ ? format(subscription.endedAt, 'dd-MM-yyyy')
64
+ : subscription.schedule?.endAt
65
+ ? format(subscription.schedule?.endAt, 'dd-MM-yyyy')
66
+ : undefined
67
+ }
68
+ ]
69
+ }),
70
+ []
71
+ )
72
+
73
+ const border = `1px solid ${colors.primary2}`
74
+
75
+ return (
76
+ <View style={{ borderLeft: border, borderRight: border }}>
77
+ <View style={{ backgroundColor: colors.primary1, borderTop: border, padding: 8 }}>
78
+ <Text style={{ fontWeight: 'bold', textTransform: 'uppercase', letterSpacing: 1 }}>{tDetails.agreement}</Text>
79
+ </View>
80
+ {Object.keys(details).map((key, i) => (
81
+ <View key={key} style={{ padding: 8, paddingBottom: 16, rowGap: 8, borderTop: border }}>
82
+ <Text style={{ textTransform: 'uppercase', color: colors.primary2, fontSize: 8, letterSpacing: 1 }}>{`${key} details`}</Text>
83
+ {details[key as keyof typeof details].map((line) => (
84
+ <LabelValue label={line.key} value={line.value} key={line.key} />
85
+ ))}
86
+ </View>
87
+ ))}
88
+ </View>
89
+ )
90
+ }
91
+
92
+ export default Details
93
+
94
+ // TODO: replace this with dump's once the package is ready
95
+ const getPlanTranslation = (plan?: PlanType | null, vehicleType?: VehicleType) => {
96
+ switch (plan) {
97
+ case 'plan60':
98
+ return vehicleType === 'motorcycle' ? '60 Months' : '60 Months (Basic)'
99
+ case 'plan36':
100
+ return '36 Months'
101
+ case 'plan24':
102
+ return '24 Months'
103
+ case 'plan12':
104
+ return '12 Months'
105
+ case 'plan1':
106
+ return 'Monthly (Ultra)'
107
+ case 'planWeekly':
108
+ return 'Weekly'
109
+ default:
110
+ return 'Unknown Plan'
111
+ }
112
+ }
113
+
114
+ const getFullMileagePackageTranslation = (plan?: MileagePackage | null) => {
115
+ switch (plan) {
116
+ case 'lite':
117
+ return 'Lite - 1,250KM'
118
+ case 'standard':
119
+ return 'Standard - 2,000KM'
120
+ case 'plus':
121
+ return 'Plus - 2,750KM'
122
+ case 'unlimited':
123
+ return 'Unlimited'
124
+ default:
125
+ return 'Unknown Mileage Package'
126
+ }
127
+ }
@@ -0,0 +1,37 @@
1
+ import { Text, View } from '@react-pdf/renderer'
2
+ import { FC } from 'react'
3
+ import { colors } from '../../theme/colors.js'
4
+ import { SubscriptionAgreementTranslation } from './translations.js'
5
+
6
+ type Props = {
7
+ tComputerGenerated: SubscriptionAgreementTranslation['computerGenerated']
8
+ tPage: SubscriptionAgreementTranslation['page']
9
+ tOf: SubscriptionAgreementTranslation['of']
10
+ }
11
+
12
+ const Footer: FC<Props> = ({ tComputerGenerated, tPage, tOf }) => {
13
+ return (
14
+ <View
15
+ fixed
16
+ style={{
17
+ justifyContent: 'space-between',
18
+ flexDirection: 'row',
19
+ paddingTop: 16,
20
+ marginTop: 16,
21
+ borderTop: `1px solid ${colors.links}`,
22
+ color: colors.primary2,
23
+ fontSize: 8,
24
+ }}
25
+ >
26
+ <Text>{tComputerGenerated}</Text>
27
+ <View style={{ flexDirection: 'row', columnGap: 4 }}>
28
+ <Text>{tPage}</Text>
29
+ <Text style={{ fontWeight: 'bold', color: colors.black }} render={({ pageNumber }) => `${pageNumber}`} />
30
+ <Text>{tOf}</Text>
31
+ <Text style={{ fontWeight: 'bold', color: colors.black }} render={({ totalPages }) => `${totalPages}`} />
32
+ </View>
33
+ </View>
34
+ )
35
+ }
36
+
37
+ export default Footer
@@ -0,0 +1,37 @@
1
+ import { Link, Text, View } from '@react-pdf/renderer'
2
+ import { FC } from 'react'
3
+ import FluxLogo from '../../components/FluxLogo.js'
4
+ import { colors } from '../../theme/colors.js'
5
+ import { SubscriptionAgreementTranslation } from './translations.js'
6
+
7
+ type Props = {
8
+ tTitle: SubscriptionAgreementTranslation['title']
9
+ userId: string
10
+ subscriptionId: string
11
+ }
12
+
13
+ const Header: FC<Props> = ({ tTitle, userId, subscriptionId }) => {
14
+ return (
15
+ <View
16
+ fixed
17
+ style={{
18
+ flexDirection: 'row',
19
+ justifyContent: 'space-between',
20
+ alignItems: 'center',
21
+ paddingBottom: 40,
22
+ // to not get pushed by the content
23
+ minHeight: 69
24
+ }}
25
+ >
26
+ <Link src="https://driveflux.com">
27
+ <FluxLogo />
28
+ </Link>
29
+ <View style={{ rowGap: 4, fontSize: 8, alignItems: 'flex-end' }}>
30
+ <Text style={{ fontWeight: 'bold' }}>{tTitle}</Text>
31
+ <Text style={{ color: colors.primary2 }}>{`FLUX-${userId}-${subscriptionId}`}</Text>
32
+ </View>
33
+ </View>
34
+ )
35
+ }
36
+
37
+ export default Header
@@ -0,0 +1,19 @@
1
+ import { Text, View } from '@react-pdf/renderer'
2
+ import { FC } from 'react'
3
+
4
+ type Props = {
5
+ label: string
6
+ value?: string | number | boolean | null
7
+ }
8
+
9
+ const LabelValue: FC<Props> = ({ label, value }) => {
10
+ return (
11
+ <View style={{ flexDirection: 'row' }}>
12
+ <Text style={{ minWidth: 120, textTransform: 'capitalize' }}>{label}</Text>
13
+ <Text>{`:`}</Text>
14
+ <Text style={{ paddingLeft: 16 }}>{`${value}` || '-'}</Text>
15
+ </View>
16
+ )
17
+ }
18
+
19
+ export default LabelValue
@@ -0,0 +1,24 @@
1
+ import { View } from '@react-pdf/renderer'
2
+ import { FC, PropsWithChildren } from 'react'
3
+ import StandardPage from '../../components/StandardPage.js'
4
+ import Footer from './Footer.js'
5
+ import Header from './Header.js'
6
+ import { SubscriptionAgreementTranslation } from './translations.js'
7
+
8
+ type Props = {
9
+ t: SubscriptionAgreementTranslation
10
+ userId: string
11
+ subscriptionId: string
12
+ }
13
+
14
+ const SubscriptionAgreementPage: FC<PropsWithChildren<Props>> = ({ t, userId, subscriptionId, children }) => {
15
+ return (
16
+ <StandardPage style={{ padding: 40, fontSize: 10 }}>
17
+ <Header tTitle={t.title} userId={userId} subscriptionId={subscriptionId} />
18
+ <View style={{ flex: 1 }}>{children}</View>
19
+ <Footer tComputerGenerated={t.computerGenerated} tPage={t.page} tOf={t.of} />
20
+ </StandardPage>
21
+ )
22
+ }
23
+
24
+ export default SubscriptionAgreementPage
@@ -0,0 +1,30 @@
1
+ import { Text, View } from '@react-pdf/renderer'
2
+ import { FC } from 'react'
3
+ import TermsOfUseSubSection from './TermsOfUseSubSection.js'
4
+ import { SubscriptionAgreementTranslation } from './translations.js'
5
+
6
+ type Props = {
7
+ t: SubscriptionAgreementTranslation['termsOfUse']
8
+ }
9
+
10
+ const TermsOfUse: FC<Props> = ({ t }) => {
11
+ return (
12
+ <View style={{ rowGap: 16 }}>
13
+ <Text style={{ fontWeight: 'bold', textTransform: 'uppercase' }}>{t.title}</Text>
14
+ <View style={{ rowGap: 16, lineHeight: 1.4 }}>
15
+ {t.content.map((section, i) => (
16
+ <View key={section.title} style={{ rowGap: 16 }}>
17
+ <Text style={{ fontWeight: 'bold' }}>{`${i + 1} ${section.title}`}</Text>
18
+ <View style={{ rowGap: 16, paddingLeft: 16 }}>
19
+ {section.subSections.map((subSection, j) => (
20
+ <TermsOfUseSubSection indices={[i + 1, j + 1]} subSection={subSection} key={`${i + 1}.${j}`} />
21
+ ))}
22
+ </View>
23
+ </View>
24
+ ))}
25
+ </View>
26
+ </View>
27
+ )
28
+ }
29
+
30
+ export default TermsOfUse
@@ -0,0 +1,44 @@
1
+ import { Text, View } from '@react-pdf/renderer'
2
+ import type { FC } from 'react'
3
+ import { useMemo } from 'react'
4
+
5
+ type TermsOfUseSubSectionType =
6
+ | string
7
+ | {
8
+ paragraph: string
9
+ subSections: TermsOfUseSubSectionType[]
10
+ }
11
+
12
+ type Props = {
13
+ indices: number[]
14
+ subSection: TermsOfUseSubSectionType
15
+ }
16
+
17
+ const TermsOfUseSubSection: FC<Props> = ({ indices, subSection }) => {
18
+ const { indicesString, paddingLeft } = useMemo(
19
+ () => ({
20
+ indicesString: indices.reduce((prev, curr, i) => `${prev}${curr}${i === indices.length - 1 ? '' : '.'}`, ''),
21
+ paddingLeft: Math.max(indices.length - 2, 0) * 16
22
+ }),
23
+ []
24
+ )
25
+
26
+ return typeof subSection === 'string' ? (
27
+ <View style={{ flexDirection: 'row', paddingLeft, columnGap: 16 }} key={indicesString}>
28
+ <Text style={{ fontWeight: 'bold' }}>{indicesString}</Text>
29
+ <Text style={{ textAlign: 'justify', flex: 1 }}>{subSection}</Text>
30
+ </View>
31
+ ) : 'subSections' in subSection ? (
32
+ <View key={indicesString} style={{ gap: 16 }}>
33
+ <View style={{ flexDirection: 'row', paddingLeft, columnGap: 16 }} key={indicesString}>
34
+ <Text style={{ fontWeight: 'bold' }}>{indicesString}</Text>
35
+ <Text style={{ textAlign: 'justify', flex: 1 }}>{subSection.paragraph}</Text>
36
+ </View>
37
+ {subSection.subSections.map((subSection, i) => (
38
+ <TermsOfUseSubSection key={`${indices}.${i}`} indices={[...indices, i + 1]} subSection={subSection} />
39
+ ))}
40
+ </View>
41
+ ) : null
42
+ }
43
+
44
+ export default TermsOfUseSubSection
@@ -0,0 +1,66 @@
1
+ import { SignatureDetails, Subscription, SubscriptionSchedule, User, Vehicle } from '@driveflux/db'
2
+ import { Document } from '@react-pdf/renderer'
3
+ import { FC, useMemo } from 'react'
4
+ import { PdfTemplatesSupportedLocales } from '../../types.js'
5
+ import { toPdf } from '../../utils.js'
6
+ import Confirmation from './Confirmation.js'
7
+ import CoverPage from './CoverPage.js'
8
+ import Details from './Details.js'
9
+ import SubscriptionAgreementPage from './SubscriptionAgreementPage.js'
10
+ import TermsOfUse from './TermsOfUse.js'
11
+ import { useTranslations } from './translations.js'
12
+
13
+ export type SubscriptionForAgreement = Subscription & {
14
+ user: User
15
+ vehicle: Vehicle
16
+ schedule?: SubscriptionSchedule | null
17
+ }
18
+
19
+ type Options = {
20
+ locale?: PdfTemplatesSupportedLocales
21
+ subscription: SubscriptionForAgreement
22
+ }
23
+ export const generateSubscriptionAgreementPdf = async ({ locale = 'en', subscription }: Options, fileName: string) => {
24
+ if (!subscription.signature) {
25
+ throw new Error('Signature not found')
26
+ }
27
+ return await toPdf(
28
+ <SubscriptionAgreement locale={locale} signature={subscription.signature} subscription={subscription} />,
29
+ fileName
30
+ )
31
+ }
32
+
33
+ type Props = {
34
+ locale?: PdfTemplatesSupportedLocales
35
+ signature: SignatureDetails
36
+ subscription: SubscriptionForAgreement
37
+ }
38
+ const SubscriptionAgreement: FC<Props> = ({ locale = 'en', signature, subscription }) => {
39
+ if (!signature.signedAt) {
40
+ throw new Error(`Missing 'signature.signedAt'`)
41
+ }
42
+
43
+ const pageProps = useMemo(() => {
44
+ return {
45
+ t: useTranslations(locale),
46
+ userId: subscription.userId,
47
+ subscriptionId: subscription.id
48
+ }
49
+ }, [])
50
+ const { t } = pageProps
51
+
52
+ return (
53
+ <Document title={t.title} language={locale}>
54
+ <SubscriptionAgreementPage {...pageProps}>
55
+ <CoverPage tTitle={t.title} tCover={t.cover} signedAt={signature.signedAt} user={subscription.user} />
56
+ </SubscriptionAgreementPage>
57
+ <SubscriptionAgreementPage {...pageProps}>
58
+ <TermsOfUse t={t.termsOfUse} />
59
+ </SubscriptionAgreementPage>
60
+ <SubscriptionAgreementPage {...pageProps}>
61
+ <Details tDetails={t.details} subscription={subscription} />
62
+ <Confirmation tConfirmation={t.confirmation} subscription={subscription} />
63
+ </SubscriptionAgreementPage>
64
+ </Document>
65
+ )
66
+ }