Compare commits
936 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fa386fb0a | ||
|
|
1c77fadc77 | ||
|
|
3fb0c12770 | ||
|
|
1968d26de8 | ||
|
|
57c243f191 | ||
|
|
e2213a8b18 | ||
|
|
dc6b6f813f | ||
|
|
3ee4612504 | ||
|
|
483c590aef | ||
|
|
64351bb3a2 | ||
|
|
7ac10dc42e | ||
|
|
54b97cb15e | ||
|
|
6e40507ea8 | ||
|
|
770a487c4d | ||
|
|
dbf963445c | ||
|
|
a00bbbfd80 | ||
|
|
f3608721ae | ||
|
|
a707c4b4dd | ||
|
|
697003d1c6 | ||
|
|
570378f88c | ||
|
|
4c97c19858 | ||
|
|
cca0539c74 | ||
|
|
d9a36bacde | ||
|
|
281050e696 | ||
|
|
3abe866e01 | ||
|
|
7ba8a863b1 | ||
|
|
579eb478ed | ||
|
|
c98738aca9 | ||
|
|
fbe7f9af37 | ||
|
|
2390beab02 | ||
|
|
fe92265201 | ||
|
|
efd7ac1a68 | ||
|
|
3a88c0f386 | ||
|
|
a173e7271e | ||
|
|
601706cc5f | ||
|
|
40c4bfb8fe | ||
|
|
d6d456856d | ||
|
|
e08e23a8b1 | ||
|
|
b156397ea0 | ||
|
|
4d1e244629 | ||
|
|
d85bb55f4f | ||
|
|
ef11c17384 | ||
|
|
b5bde9da43 | ||
|
|
76f842f8df | ||
|
|
959ec06b2c | ||
|
|
d5be9eb045 | ||
|
|
e4222beac3 | ||
|
|
c4bae634ec | ||
|
|
5f7ffd737f | ||
|
|
3720591dda | ||
|
|
34c4903bad | ||
|
|
c13413170f | ||
|
|
04fbf454aa | ||
|
|
41972f70b3 | ||
|
|
816584a93e | ||
|
|
4d906e2e96 | ||
|
|
60232968da | ||
|
|
b8449fc588 | ||
|
|
bace4600fd | ||
|
|
fb384544df | ||
|
|
8ed76db221 | ||
|
|
64746f29d7 | ||
|
|
eed0b9b0e8 | ||
|
|
0ac72e2ff4 | ||
|
|
a3fd957971 | ||
|
|
0509f5224f | ||
|
|
3b463b64be | ||
|
|
faf3a4337a | ||
|
|
3d3af94a24 | ||
|
|
eaeece5f5d | ||
|
|
1b953c4c78 | ||
|
|
28fcdc3496 | ||
|
|
c2eb3c5cb0 | ||
|
|
64c8df222d | ||
|
|
67a15563f3 | ||
|
|
0e98cc2294 | ||
|
|
b05b138550 | ||
|
|
5955732b22 | ||
|
|
30b4b4fbdd | ||
|
|
709c36e623 | ||
|
|
833d9fd76c | ||
|
|
fab719b977 | ||
|
|
4232a23fa5 | ||
|
|
22b0b7c47f | ||
|
|
ebe069b162 | ||
|
|
a82dd4039b | ||
|
|
edfe3e7095 | ||
|
|
c3a99e78e4 | ||
|
|
dbfb5141a4 | ||
|
|
2fc75aec76 | ||
|
|
87256f1fa7 | ||
|
|
3c80e194a8 | ||
|
|
cbd6b738a0 | ||
|
|
dc61925e77 | ||
|
|
9eb4e7a125 | ||
|
|
494ad56147 | ||
|
|
35039f5643 | ||
|
|
875b7ac108 | ||
|
|
5fc4f43217 | ||
|
|
d50be5cdf8 | ||
|
|
0bb009d897 | ||
|
|
76c61bc52d | ||
|
|
3f5c983e44 | ||
|
|
72d98a5912 | ||
|
|
6186480a94 | ||
|
|
e138a05194 | ||
|
|
593777ffa1 | ||
|
|
a83a4057db | ||
|
|
3cda4f2c81 | ||
|
|
033060c1a5 | ||
|
|
4a1ab6cc02 | ||
|
|
ede229944f | ||
|
|
17cf7e5ee5 | ||
|
|
9724ed565b | ||
|
|
55b043bb7b | ||
|
|
c3b8d68c74 | ||
|
|
d6a90c3271 | ||
|
|
c90c83a10e | ||
|
|
bc3f50d702 | ||
|
|
239914cab0 | ||
|
|
ac1559b7dd | ||
|
|
95e5f36a48 | ||
|
|
120a4b556d | ||
|
|
519ec667da | ||
|
|
a62674f187 | ||
|
|
47fb3b2951 | ||
|
|
a6dac66f8e | ||
|
|
dc34e4463d | ||
|
|
72b4f342d9 | ||
|
|
905f99043b | ||
|
|
d4c265c014 | ||
|
|
8ffa38bcc0 | ||
|
|
40a3ab09aa | ||
|
|
89e7846519 | ||
|
|
c1bfed5e7d | ||
|
|
262bfba6b3 | ||
|
|
74c0ce96e5 | ||
|
|
6521ec7ab7 | ||
|
|
839afb6f4b | ||
|
|
3ee937757b | ||
|
|
6ef5da91df | ||
|
|
15994f9b8b | ||
|
|
c85d3fdd00 | ||
|
|
4f21e2c0a7 | ||
|
|
30661d5d67 | ||
|
|
a66d947838 | ||
|
|
10d25041b6 | ||
|
|
3bbfc631a1 | ||
|
|
468b387499 | ||
|
|
f0fdc46ef3 | ||
|
|
4bc9824400 | ||
|
|
a84df90ac3 | ||
|
|
e94c9d9409 | ||
|
|
1558b9c399 | ||
|
|
e946ffa22c | ||
|
|
9adf213ef0 | ||
|
|
df524d4b89 | ||
|
|
4bc0f6f53f | ||
|
|
8ffe9cd9a6 | ||
|
|
50e48fac84 | ||
|
|
cfdfa2ead1 | ||
|
|
61449c4d66 | ||
|
|
ec778ece2f | ||
|
|
bd76364769 | ||
|
|
fa79c2c650 | ||
|
|
8cf99fd6c0 | ||
|
|
fb2c11cae2 | ||
|
|
cb242413f8 | ||
|
|
3242f01146 | ||
|
|
2485b46b30 | ||
|
|
578a02039a | ||
|
|
828719085e | ||
|
|
0cb982f246 | ||
|
|
468821c9e0 | ||
|
|
b945c98463 | ||
|
|
62d67275c3 | ||
|
|
138fbaa64d | ||
|
|
37ebb6b4d6 | ||
|
|
8825c166ed | ||
|
|
be310ae251 | ||
|
|
89d46e6487 | ||
|
|
423a9200cd | ||
|
|
cd525d0708 | ||
|
|
f9dae4bac4 | ||
|
|
a3898f6341 | ||
|
|
3eb5f16544 | ||
|
|
3791a2e5d4 | ||
|
|
5f113608b4 | ||
|
|
625d87cf6b | ||
|
|
11dfaff42e | ||
|
|
5d559e2aa0 | ||
|
|
c569fe2c51 | ||
|
|
ed9ad72b02 | ||
|
|
30c8e09cac | ||
|
|
6f4e93bbdc | ||
|
|
426a12288b | ||
|
|
8e550d8392 | ||
|
|
841b45c169 | ||
|
|
669006015a | ||
|
|
18492a2e44 | ||
|
|
ecc8657b0d | ||
|
|
a8f746a59f | ||
|
|
13af1ab471 | ||
|
|
8f26d7917c | ||
|
|
7233aafb55 | ||
|
|
f9222183e5 | ||
|
|
8afc7ce29f | ||
|
|
9343ab9f30 | ||
|
|
5854ff0ecc | ||
|
|
441ecd9759 | ||
|
|
8a71845830 | ||
|
|
e91eaa3866 | ||
|
|
b8381bf2f4 | ||
|
|
551ea6a9b3 | ||
|
|
5eea53eecf | ||
|
|
d5cf331113 | ||
|
|
a2deea8b5e | ||
|
|
563e6faa49 | ||
|
|
4cb1af5703 | ||
|
|
3a9f1960c6 | ||
|
|
7ee545f17d | ||
|
|
e3852a2b20 | ||
|
|
3d1b041694 | ||
|
|
2b8496222c | ||
|
|
2bf631e0f4 | ||
|
|
e713a358c7 | ||
|
|
bc94c96a55 | ||
|
|
8973b5c4b6 | ||
|
|
a937c8474d | ||
|
|
d8c5461a58 | ||
|
|
b5e8bd3f8c | ||
|
|
76107dd62e | ||
|
|
2b48dfe107 | ||
|
|
4aeb38f0e6 | ||
|
|
c46e9f2ca0 | ||
|
|
2ca26d149a | ||
|
|
bab325bccc | ||
|
|
4b2560b228 | ||
|
|
a740d03ca2 | ||
|
|
e43572ce9c | ||
|
|
10c2739629 | ||
|
|
0e1d39cb86 | ||
|
|
9262d5c617 | ||
|
|
09ae9c1d1c | ||
|
|
a068611536 | ||
|
|
0083708e37 | ||
|
|
95b6c1593a | ||
|
|
abd4611048 | ||
|
|
fa4a0b5e48 | ||
|
|
e8c8fce012 | ||
|
|
236115071e | ||
|
|
39175a8be8 | ||
|
|
db11a921ad | ||
|
|
200ca602e1 | ||
|
|
5c533e884e | ||
|
|
740e2f24bc | ||
|
|
e01dfa0258 | ||
|
|
68abf0e471 | ||
|
|
df601c3088 | ||
|
|
3486aad00b | ||
|
|
7d9efab5b9 | ||
|
|
3dd951792f | ||
|
|
926e68f854 | ||
|
|
622dfa18ca | ||
|
|
e6250edfe4 | ||
|
|
54ee91d2e5 | ||
|
|
f4a487fb3c | ||
|
|
c772af8f0f | ||
|
|
07dc5b0444 | ||
|
|
af782db541 | ||
|
|
fcc932441e | ||
|
|
a78777a85b | ||
|
|
07cd93a649 | ||
|
|
3029d8dd2c | ||
|
|
0896d315f3 | ||
|
|
20b9c6c8a3 | ||
|
|
291708ff8c | ||
|
|
952f713a5d | ||
|
|
47e6f10b96 | ||
|
|
bcde9ac0ac | ||
|
|
106b1c2c49 | ||
|
|
44958f876f | ||
|
|
17bbaad975 | ||
|
|
560c0a05a0 | ||
|
|
0a5d60913e | ||
|
|
ec76c3fd8c | ||
|
|
8de2513582 | ||
|
|
9c84c9761f | ||
|
|
8d0a58a3a3 | ||
|
|
e9dc59cfc0 | ||
|
|
1de7dfa746 | ||
|
|
897d67baea | ||
|
|
a43ab75694 | ||
|
|
e5e972dc7d | ||
|
|
e2159de9f3 | ||
|
|
7161853006 | ||
|
|
42dc1d5acf | ||
|
|
b4f56071a0 | ||
|
|
3fcb75a9e9 | ||
|
|
339676a2eb | ||
|
|
68d25e51f2 | ||
|
|
55bf312b87 | ||
|
|
313ab16451 | ||
|
|
d0b140a944 | ||
|
|
5a12a1e65f | ||
|
|
5cd7ba5afc | ||
|
|
537c001e2f | ||
|
|
20d619df95 | ||
|
|
a0fe57dfa4 | ||
|
|
ec86dfa596 | ||
|
|
4c6b8c4686 | ||
|
|
673077066e | ||
|
|
ee78b08822 | ||
|
|
7a5e77f3be | ||
|
|
506248bfec | ||
|
|
8981ef32e3 | ||
|
|
bce7c8f486 | ||
|
|
9a97cb6810 | ||
|
|
41c601784a | ||
|
|
85aee0b47a | ||
|
|
ba831ebc9a | ||
|
|
f1bc1697b6 | ||
|
|
0f9c74be68 | ||
|
|
976b740156 | ||
|
|
be0c8a7cf5 | ||
|
|
d31055f263 | ||
|
|
812ec463f5 | ||
|
|
26e82bcc50 | ||
|
|
d926f6b8c6 | ||
|
|
fafefea5ee | ||
|
|
9eaff734d8 | ||
|
|
7526e93f4e | ||
|
|
c73bb5209a | ||
|
|
29812d2428 | ||
|
|
405e829554 | ||
|
|
b6dd976a96 | ||
|
|
8758d27ddc | ||
|
|
47b6a584c1 | ||
|
|
ea8217a6ab | ||
|
|
18348a6955 | ||
|
|
8aba4d5b26 | ||
|
|
6dfc015d3b | ||
|
|
b929493f01 | ||
|
|
551dff00ce | ||
|
|
1f0938a87c | ||
|
|
1fbe92f7e6 | ||
|
|
07f4ad2b3c | ||
|
|
ec3c74180b | ||
|
|
61a9941ce6 | ||
|
|
a15d3d15ab | ||
|
|
d8c614c911 | ||
|
|
236798d32a | ||
|
|
d06f9e8d5d | ||
|
|
d45f102940 | ||
|
|
8247e54988 | ||
|
|
0f52866516 | ||
|
|
720b23878f | ||
|
|
16e0874f45 | ||
|
|
3e2569b693 | ||
|
|
98fb5d40b2 | ||
|
|
b836a5eb8a | ||
|
|
fa9cb85755 | ||
|
|
84f272eaad | ||
|
|
be9647fb3b | ||
|
|
743ff67ef6 | ||
|
|
b6657bc20e | ||
|
|
88f635337f | ||
|
|
6c1ab84e1a | ||
|
|
7b5823a598 | ||
|
|
1fc4c01878 | ||
|
|
3c6143c670 | ||
|
|
d6545edc25 | ||
|
|
1e4c26ac77 | ||
|
|
a6347f1256 | ||
|
|
9ff121c615 | ||
|
|
076055f9c2 | ||
|
|
be2a0fac8b | ||
|
|
63c9d73a3d | ||
|
|
b8b919ca11 | ||
|
|
905c3ee129 | ||
|
|
f1b7a2235d | ||
|
|
59f5e08897 | ||
|
|
4ffef72a25 | ||
|
|
aa35f81b28 | ||
|
|
c0060128f9 | ||
|
|
84c2894e84 | ||
|
|
659f2364e6 | ||
|
|
760c78e897 | ||
|
|
11997a8d7e | ||
|
|
d508c974ad | ||
|
|
0311ef3787 | ||
|
|
2ce3be3372 | ||
|
|
cdaabd0f96 | ||
|
|
38fc769539 | ||
|
|
04ffddc52c | ||
|
|
8223878f08 | ||
|
|
a202a8eab4 | ||
|
|
5c7c83b42a | ||
|
|
78f314f791 | ||
|
|
f7d5341988 | ||
|
|
af9cbe50b2 | ||
|
|
3ff963192a | ||
|
|
97c7654073 | ||
|
|
ce9d61adc2 | ||
|
|
6337afff9a | ||
|
|
d9b43a09cd | ||
|
|
5da82192df | ||
|
|
60f48bec27 | ||
|
|
e5fd1f7510 | ||
|
|
0441300628 | ||
|
|
f9396716dd | ||
|
|
60af89ac0e | ||
|
|
d56ea31ea7 | ||
|
|
21d504013c | ||
|
|
48c1f51797 | ||
|
|
dbe92f5f3d | ||
|
|
2f27c3e8a7 | ||
|
|
99750e112b | ||
|
|
924b64f38d | ||
|
|
4314580258 | ||
|
|
4a3052d618 | ||
|
|
ad3daf301f | ||
|
|
0d2474e66a | ||
|
|
bce2688832 | ||
|
|
a659c40486 | ||
|
|
786e0c4092 | ||
|
|
d5363cc4b0 | ||
|
|
0e975d2a61 | ||
|
|
1c14794ed8 | ||
|
|
0c2b5e707d | ||
|
|
0a6fa6e5ce | ||
|
|
9348b73c4c | ||
|
|
cfa271066f | ||
|
|
d3fc130082 | ||
|
|
be8de4f960 | ||
|
|
b0ca711118 | ||
|
|
5cecdc982e | ||
|
|
68bf18e737 | ||
|
|
a373439ac0 | ||
|
|
04b023c63e | ||
|
|
da5a74abae | ||
|
|
f9cefe97a8 | ||
|
|
6ec47e4b6c | ||
|
|
eae3b98078 | ||
|
|
774ffa0177 | ||
|
|
ca284f8d7e | ||
|
|
0c15dae30e | ||
|
|
a38b118a6d | ||
|
|
6c7549a7c9 | ||
|
|
942b379de1 | ||
|
|
436151f804 | ||
|
|
72adafb895 | ||
|
|
38cb342999 | ||
|
|
b1eb69f6d3 | ||
|
|
d2b1db828e | ||
|
|
06ae0f24d6 | ||
|
|
32a55232e9 | ||
|
|
9e3c8ec51f | ||
|
|
d3b8c558a5 | ||
|
|
c30a6ab6e2 | ||
|
|
3b2bc6bb26 | ||
|
|
41fac4000b | ||
|
|
d347ba5cf4 | ||
|
|
aae6380559 | ||
|
|
a3239dd1da | ||
|
|
30efce5ac3 | ||
|
|
ae311b62f0 | ||
|
|
385b35e264 | ||
|
|
35893e06b5 | ||
|
|
6cf77849c8 | ||
|
|
6440d0ce61 | ||
|
|
9ed5f82288 | ||
|
|
9af5705d40 | ||
|
|
ccbbc1a290 | ||
|
|
fb5e0ec73b | ||
|
|
00414e70f9 | ||
|
|
37ca536f6e | ||
|
|
52cf6c4abd | ||
|
|
cde1ec2e7e | ||
|
|
63fc14eeae | ||
|
|
e96f91de9e | ||
|
|
099dd6bd9e | ||
|
|
3110babfaa | ||
|
|
15c0a05e55 | ||
|
|
5f27f2b103 | ||
|
|
4a36f4ac1c | ||
|
|
9590c46861 | ||
|
|
1732ae42ea | ||
|
|
dbae9ecd84 | ||
|
|
49ad3f9395 | ||
|
|
9ae3bfa5be | ||
|
|
06be30d5fe | ||
|
|
a270b9552a | ||
|
|
bb5141eb50 | ||
|
|
f340c19ae8 | ||
|
|
f824e4fd2b | ||
|
|
726389c941 | ||
|
|
caad06353e | ||
|
|
6d6ebbffc1 | ||
|
|
32fc6ba5b1 | ||
|
|
538faf945d | ||
|
|
c33afc9b0c | ||
|
|
419dbb05d8 | ||
|
|
6b2b7e9b6c | ||
|
|
a7b7c58d80 | ||
|
|
e56864050f | ||
|
|
76823c4d40 | ||
|
|
a56456c1b6 | ||
|
|
8bceee2982 | ||
|
|
bf05354bcd | ||
|
|
885d2840a9 | ||
|
|
5d2601000b | ||
|
|
e2dc22f38f | ||
|
|
0f272147ce | ||
|
|
bd9bbbed2c | ||
|
|
f36bc32694 | ||
|
|
86eb33674d | ||
|
|
273019af59 | ||
|
|
6d09d1281c | ||
|
|
0e860c480a | ||
|
|
fbd26bdfec | ||
|
|
47cda11c90 | ||
|
|
133ee1d44d | ||
|
|
94bb6be20a | ||
|
|
c185072482 | ||
|
|
2dcea906ec | ||
|
|
2177ac39c9 | ||
|
|
56151850dd | ||
|
|
e5238ca78b | ||
|
|
b8db69b5b7 | ||
|
|
ac3db9efa3 | ||
|
|
d89aa64e57 | ||
|
|
4c6435e569 | ||
|
|
b31656791c | ||
|
|
27a49d1534 | ||
|
|
6f7e6c2fc0 | ||
|
|
601f6cbc5f | ||
|
|
f3b66239a6 | ||
|
|
69631bf5fc | ||
|
|
0870f01b13 | ||
|
|
b928c95a3b | ||
|
|
0d30657e90 | ||
|
|
c6306fc8a6 | ||
|
|
a05448c48a | ||
|
|
0cb03dc517 | ||
|
|
0afdaa641d | ||
|
|
ce26e967d9 | ||
|
|
dfc3a85557 | ||
|
|
caa074c218 | ||
|
|
5f079592a7 | ||
|
|
53f8bb3f6f | ||
|
|
87d488d8b6 | ||
|
|
b8d2170eed | ||
|
|
499417fe45 | ||
|
|
f8b125e462 | ||
|
|
5d5c745c2a | ||
|
|
4d3e1391e7 | ||
|
|
01c2bcfab7 | ||
|
|
7d3b229731 | ||
|
|
7523971718 | ||
|
|
6a5b3af8c3 | ||
|
|
dd2fc19d05 | ||
|
|
05474d9e89 | ||
|
|
73eb62af1d | ||
|
|
aa8c09e43e | ||
|
|
99a50cdc95 | ||
|
|
97da6dd066 | ||
|
|
36ed1eff26 | ||
|
|
a3d896329e | ||
|
|
6ff26d4bfa | ||
|
|
9d7d302a7a | ||
|
|
e8ec5dbf94 | ||
|
|
16246487e4 | ||
|
|
b038aa2f5b | ||
|
|
dfbaa5449e | ||
|
|
725fcb5e7a | ||
|
|
c8f6387b2b | ||
|
|
c5fd82bd66 | ||
|
|
57a26bbe56 | ||
|
|
0d2098c364 | ||
|
|
2b6795cc61 | ||
|
|
f114df7739 | ||
|
|
8361c30fa3 | ||
|
|
1119d51b97 | ||
|
|
6aa2170f84 | ||
|
|
db056aa78a | ||
|
|
3cabfcbe5b | ||
|
|
7706bcd8f0 | ||
|
|
04a16fe8fe | ||
|
|
19357e6548 | ||
|
|
5ffe9a45c5 | ||
|
|
c43f9c034b | ||
|
|
d8bf1c926c | ||
|
|
21029f549e | ||
|
|
999c12b369 | ||
|
|
1ef6d84839 | ||
|
|
44e69bf36c | ||
|
|
1d15936bae | ||
|
|
72e20dfb56 | ||
|
|
e1e6681938 | ||
|
|
db658bb0b8 | ||
|
|
958ae089c9 | ||
|
|
621f078661 | ||
|
|
6599003fc5 | ||
|
|
098a00737c | ||
|
|
558b43832a | ||
|
|
676882534b | ||
|
|
7cb204d94b | ||
|
|
fac1e129e8 | ||
|
|
2b6e870937 | ||
|
|
f72700258e | ||
|
|
1e732b4c97 | ||
|
|
5a93f3a075 | ||
|
|
03fa80ec09 | ||
|
|
4de3544e2c | ||
|
|
31cf19a87e | ||
|
|
dff4df747f | ||
|
|
c46b168ccc | ||
|
|
089a4c4460 | ||
|
|
8de77822b1 | ||
|
|
b17036b8d3 | ||
|
|
400a0d3df2 | ||
|
|
d22da25c73 | ||
|
|
ea22f94d43 | ||
|
|
d0bc25f0cf | ||
|
|
08eb9e3a6e | ||
|
|
9c31f3e0ea | ||
|
|
9d8d211677 | ||
|
|
6319c77731 | ||
|
|
1ba857c3ce | ||
|
|
a3b43cec27 | ||
|
|
338fb79f57 | ||
|
|
7dff8331aa | ||
|
|
a3400f4933 | ||
|
|
1e5e70a5c2 | ||
|
|
abe973337c | ||
|
|
dd33c4d9dc | ||
|
|
ff682d97cf | ||
|
|
16d5c41294 | ||
|
|
3197d74ce4 | ||
|
|
ec44fae2b3 | ||
|
|
904637487d | ||
|
|
e82a51c607 | ||
|
|
74a50e5323 | ||
|
|
fe36cf84cc | ||
|
|
c04742e160 | ||
|
|
854958d9e7 | ||
|
|
2484b7948b | ||
|
|
826aceaebe | ||
|
|
23cac31428 | ||
|
|
25ae9f3833 | ||
|
|
77e19c4a24 | ||
|
|
f94828a926 | ||
|
|
95749ce765 | ||
|
|
922dfb2c64 | ||
|
|
5831cb7d7b | ||
|
|
b20c6ca555 | ||
|
|
298018f96f | ||
|
|
39539f2cdf | ||
|
|
2a728a64ad | ||
|
|
7d64044711 | ||
|
|
4421410990 | ||
|
|
2c3bb15355 | ||
|
|
4f489ea50c | ||
|
|
adfce73cb6 | ||
|
|
bdc1297034 | ||
|
|
d2e8ea08df | ||
|
|
1dba15331a | ||
|
|
ad6fc6346b | ||
|
|
4f21f3a214 | ||
|
|
29875d4210 | ||
|
|
08378d7001 | ||
|
|
65a1358b93 | ||
|
|
4663f825a1 | ||
|
|
8d2adf0140 | ||
|
|
bbac34a0e5 | ||
|
|
7cc30ee3ef | ||
|
|
a0d1984103 | ||
|
|
5fe0ac3d47 | ||
|
|
a053a6c81a | ||
|
|
68f6eaa391 | ||
|
|
38b780827d | ||
|
|
f0534bd6a5 | ||
|
|
3d11d733e0 | ||
|
|
f4f67be661 | ||
|
|
080ae345a6 | ||
|
|
bee450d786 | ||
|
|
167cc2608d | ||
|
|
3ea7738564 | ||
|
|
01b1830cbd | ||
|
|
2a886131c4 | ||
|
|
e894dead63 | ||
|
|
9cd3c0c5ad | ||
|
|
d6ed766d1b | ||
|
|
a032d06eea | ||
|
|
936d2955d9 | ||
|
|
b7e5165999 | ||
|
|
60d5a44d5d | ||
|
|
5b0443c70f | ||
|
|
22a8b63b3a | ||
|
|
c170bc31a3 | ||
|
|
55dc734cf0 | ||
|
|
7073234b1e | ||
|
|
d783f96404 | ||
|
|
043ddaf293 | ||
|
|
adc2d382cb | ||
|
|
e353d7fb4f | ||
|
|
d7b0b2515e | ||
|
|
8c65594ec3 | ||
|
|
15f0835d76 | ||
|
|
3d9c644c63 | ||
|
|
2650e53844 | ||
|
|
ede549f80c | ||
|
|
49f5a8aef1 | ||
|
|
6c22b46005 | ||
|
|
e8c9863ef1 | ||
|
|
a3f34177bb | ||
|
|
fd4d258d05 | ||
|
|
73bead91f9 | ||
|
|
af85a1f7d7 | ||
|
|
c08ea4eebf | ||
|
|
d66d2d55c2 | ||
|
|
2625b4427c | ||
|
|
ffa3c7ee2f | ||
|
|
687cf7bbe9 | ||
|
|
3707a1bc04 | ||
|
|
0035dae54b | ||
|
|
0cc6cf9663 | ||
|
|
a137773b82 | ||
|
|
fe0102034a | ||
|
|
d98d5d8f62 | ||
|
|
c79fb7e141 | ||
|
|
45e00d2241 | ||
|
|
fe1082e5e5 | ||
|
|
89c62999be | ||
|
|
a84e8ae33e | ||
|
|
1b838091e7 | ||
|
|
09455123e1 | ||
|
|
3fad2dd5d2 | ||
|
|
086963a497 | ||
|
|
512d820c38 | ||
|
|
082707d012 | ||
|
|
8250341399 | ||
|
|
aab486c6fd | ||
|
|
fa76ea5051 | ||
|
|
69abdd6977 | ||
|
|
0dde29f061 | ||
|
|
40be9e0184 | ||
|
|
3c8579d7e3 | ||
|
|
f05d260e91 | ||
|
|
72f89e30f8 | ||
|
|
8c07b2d655 | ||
|
|
99a18de4b1 | ||
|
|
4d89501af4 | ||
|
|
d2dc3f26fe | ||
|
|
75aae47da8 | ||
|
|
47f2689a4c | ||
|
|
0aca4567c2 | ||
|
|
1fa91f7cf7 | ||
|
|
e34539d78e | ||
|
|
1f3f7b2878 | ||
|
|
6181758a16 | ||
|
|
e3bdc831bc | ||
|
|
6884c3f086 | ||
|
|
d149c44097 | ||
|
|
b14f4ec0a5 | ||
|
|
51e38ba3b2 | ||
|
|
c644b4ec2d | ||
|
|
eae4bcb9b9 | ||
|
|
346abc0483 | ||
|
|
6f66fa82e5 | ||
|
|
736db7681f | ||
|
|
15259a6b18 | ||
|
|
fecdb42ce5 | ||
|
|
a6949bff42 | ||
|
|
1983fca5bb | ||
|
|
f00a81d51c | ||
|
|
8a5e2d3ebb | ||
|
|
9f07463731 | ||
|
|
45716754e6 | ||
|
|
2ef6f297de | ||
|
|
c4b02026fc | ||
|
|
f6cbc47e98 | ||
|
|
7a5f1e3820 | ||
|
|
98f8bdfa12 | ||
|
|
6926d4eed0 | ||
|
|
c7dc919d0a | ||
|
|
a67d5f2694 | ||
|
|
2e8dbc02b2 | ||
|
|
17cbb5c7dd | ||
|
|
54154a40da | ||
|
|
62fbf089a2 | ||
|
|
0d6fd6627a | ||
|
|
284745fe20 | ||
|
|
7b008d6d6b | ||
|
|
fa8d09c484 | ||
|
|
f9f5337e3c | ||
|
|
12ce4aa8ef | ||
|
|
baae0af3c8 | ||
|
|
2fd19b4877 | ||
|
|
f71ae73584 | ||
|
|
5ddaa87b4d | ||
|
|
361b1b0559 | ||
|
|
560e3cc3d6 | ||
|
|
1f3e8ac1a0 | ||
|
|
7177cb37c7 | ||
|
|
08e23c7c83 | ||
|
|
caa73c2efe | ||
|
|
baca28f704 | ||
|
|
343f76d11f | ||
|
|
6a222d29bd | ||
|
|
f1d8f49ef2 | ||
|
|
6066b88f81 | ||
|
|
e2aca7ac5a | ||
|
|
cd796d2141 | ||
|
|
f7368e4824 | ||
|
|
0cfd8ce22b | ||
|
|
44d061052a | ||
|
|
94658707fa | ||
|
|
ad0c591be5 | ||
|
|
d15a9652e0 | ||
|
|
3f18707e72 | ||
|
|
aeb3369914 | ||
|
|
c9ce608e1f | ||
|
|
1eeccb9153 | ||
|
|
aa75f56e62 | ||
|
|
5d96c6ac76 | ||
|
|
b565099707 | ||
|
|
fecd3ae06e | ||
|
|
ae347bdeed | ||
|
|
f3de5c7bb7 | ||
|
|
e52f318750 | ||
|
|
eb90087624 | ||
|
|
31b4cbecba | ||
|
|
fc7ddb0b63 | ||
|
|
9808df89f1 | ||
|
|
f485a5e141 | ||
|
|
24eeb90d48 | ||
|
|
299358f1d2 | ||
|
|
381b6d8191 | ||
|
|
5425211635 | ||
|
|
cbeb61da86 | ||
|
|
51329edd62 | ||
|
|
c3be38c1bc | ||
|
|
a612a61772 | ||
|
|
e51b0cbd42 | ||
|
|
307c6d5ca1 | ||
|
|
e86f1a792f | ||
|
|
65a081aa97 | ||
|
|
450fe8134d | ||
|
|
62d9be0baf | ||
|
|
7422281f31 | ||
|
|
ecaa1ece42 | ||
|
|
2ca3f3b2e2 | ||
|
|
bcd36e9619 | ||
|
|
5b8a7145be | ||
|
|
10869cde29 | ||
|
|
8dcca64fb2 | ||
|
|
ee6a0b724b | ||
|
|
501d5b08f4 | ||
|
|
3513dfcfaa | ||
|
|
c2d29804d8 | ||
|
|
8909b5c693 | ||
|
|
f177f6a6ea | ||
|
|
19f67fe64e | ||
|
|
aa4ff59618 | ||
|
|
0abb0f7cef | ||
|
|
aa8f606f81 | ||
|
|
a3acc1961f | ||
|
|
7bafae01cf | ||
|
|
2338d0dd51 | ||
|
|
c0dd47fd02 | ||
|
|
f4a14c50f3 | ||
|
|
ac287cfffb | ||
|
|
516c486e81 | ||
|
|
7d9d96cf00 | ||
|
|
75f295cbfc | ||
|
|
9037ed3e70 | ||
|
|
0fb69e90ea | ||
|
|
7ed97a7a48 | ||
|
|
9e48a1fca7 | ||
|
|
98e279542d | ||
|
|
2a31ccc458 | ||
|
|
f7efde7baf | ||
|
|
bbb08d75ee | ||
|
|
aaa8b16657 | ||
|
|
3a797fc653 | ||
|
|
1aaad1d4e1 | ||
|
|
77743a2e4b | ||
|
|
3b834e98d0 | ||
|
|
f7837e3369 | ||
|
|
f09549b546 | ||
|
|
74c54a38be | ||
|
|
e4b56a4aa4 | ||
|
|
3c99a88708 | ||
|
|
9914504902 | ||
|
|
ac84562108 | ||
|
|
3cb4b0c60c | ||
|
|
5db24d7770 | ||
|
|
e472deaee6 | ||
|
|
4df48013ef | ||
|
|
d32a4ba638 | ||
|
|
a063733564 | ||
|
|
eea6b7795c | ||
|
|
009f14e2c7 | ||
|
|
647dee1360 | ||
|
|
fea522bf5c | ||
|
|
4c610a7014 | ||
|
|
433002e637 | ||
|
|
8dcd160817 | ||
|
|
d81a4b33ae | ||
|
|
6cc16430e3 | ||
|
|
6200e7f593 | ||
|
|
080f75f141 | ||
|
|
e07f5cf4c8 | ||
|
|
2b8bbfa7cf | ||
|
|
8c6306f9b7 | ||
|
|
b420fb7bbe | ||
|
|
cdc9b692e3 | ||
|
|
b2348b4c91 | ||
|
|
83a18b71af | ||
|
|
b43dfc4cc4 | ||
|
|
8174efded1 | ||
|
|
3534ba0b6d | ||
|
|
1aef211728 | ||
|
|
2456f24a4e | ||
|
|
64b51a6bca | ||
|
|
548adf7f33 | ||
|
|
826c0e5f4d | ||
|
|
c7c1b918cd | ||
|
|
c07b1a7f2c | ||
|
|
c76a44274b | ||
|
|
10c974f7a0 | ||
|
|
3af960b444 | ||
|
|
f9ca17ad99 | ||
|
|
f3b565bed5 |
5
.auditrc
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
exports.exceptions = [
|
||||
// Add exceptions to audit script:
|
||||
// // Severity: low, lodash (< 4.17.5), used heavily in our CRA fork
|
||||
// "https://npmjs.com/advisories/577",
|
||||
];
|
||||
|
|
@ -2,7 +2,7 @@ version: 2
|
|||
jobs:
|
||||
format:
|
||||
docker:
|
||||
- image: circleci/node:8.11
|
||||
- image: circleci/node:10.14
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
command: yarn run format-ci
|
||||
test:
|
||||
docker:
|
||||
- image: circleci/node:8.11
|
||||
- image: circleci/node:10.14
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
|
@ -48,7 +48,7 @@ jobs:
|
|||
command: yarn run test-ci
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:8.11
|
||||
- image: circleci/node:10.14
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
|
@ -71,7 +71,7 @@ jobs:
|
|||
command: yarn run build
|
||||
audit:
|
||||
docker:
|
||||
- image: circleci/node:8.11
|
||||
- image: circleci/node:10.14
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
|
|
@ -99,4 +99,3 @@ workflows:
|
|||
- format
|
||||
- test
|
||||
- build
|
||||
- audit
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
|
||||
# Mandatory configuration
|
||||
#
|
||||
REACT_APP_SHARETRIBE_SDK_CLIENT_ID=
|
||||
# Note: You also need to set Stripe secret key in Flex Console.
|
||||
#
|
||||
REACT_APP_SHARETRIBE_SDK_CLIENT_ID=change-me
|
||||
REACT_APP_STRIPE_PUBLISHABLE_KEY=
|
||||
REACT_APP_MAPBOX_ACCESS_TOKEN=
|
||||
|
||||
|
|
@ -11,7 +13,6 @@ REACT_APP_MAPBOX_ACCESS_TOKEN=
|
|||
# Defaults
|
||||
#
|
||||
|
||||
REACT_APP_SHARETRIBE_SDK_BASE_URL=https://flex-api.sharetribe.com
|
||||
REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY=USD
|
||||
REACT_APP_CANONICAL_ROOT_URL=http://localhost:3000
|
||||
|
||||
|
|
@ -26,8 +27,7 @@ REACT_APP_ENV=production
|
|||
|
||||
# REACT_APP_SHARETRIBE_USING_SSL=true
|
||||
# SERVER_SHARETRIBE_TRUST_PROXY=true
|
||||
# REACT_APP_PUBLIC_SENTRY_DSN=change-me
|
||||
# SERVER_SENTRY_DSN=change-me
|
||||
# REACT_APP_SENTRY_DSN=change-me
|
||||
# REACT_APP_CSP=report
|
||||
# BASIC_AUTH_USERNAME=sharetribe
|
||||
# BASIC_AUTH_PASSWORD=secret
|
||||
|
|
|
|||
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Describe the bug** A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce** Steps to reproduce the behavior:
|
||||
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior** A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots** If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context** Add any other context about the problem here.
|
||||
10
.nsprc
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"exceptions": [
|
||||
"https://nodesecurity.io/advisories/532",
|
||||
"https://nodesecurity.io/advisories/157",
|
||||
"https://nodesecurity.io/advisories/577",
|
||||
"https://nodesecurity.io/advisories/654",
|
||||
"https://nodesecurity.io/advisories/664",
|
||||
"https://nodesecurity.io/advisories/678"
|
||||
]
|
||||
}
|
||||
|
|
@ -2,4 +2,6 @@ build/
|
|||
coverage/
|
||||
node_modules/
|
||||
public/
|
||||
src/components/index.js
|
||||
src/containers/index.js
|
||||
src/forms/index.js
|
||||
|
|
|
|||
852
CHANGELOG.md
|
|
@ -4,84 +4,742 @@ We are not following semantic versioning in this template app since any change c
|
|||
a breaking change for forked customization projects. We are still experimenting with what is a good
|
||||
way to update this template, but currently, we follow a pattern:
|
||||
|
||||
* Major version change (v**X**.0.0): Changes to several pages and other components. Consider
|
||||
- Major version change (v**X**.0.0): Changes to several pages and other components. Consider
|
||||
implementing this without merging upstream (we'll provide instructions).
|
||||
* Minor version change (v0.**X**.0): New features and changes to a single page. These are likely to
|
||||
- Minor version change (v0.**X**.0): New features and changes to a single page. These are likely to
|
||||
cause conflicts.
|
||||
* Patch (v0.0.**X**): Bug fixes and small changes to components.
|
||||
- Patch (v0.0.**X**): Bug fixes and small changes to components.
|
||||
|
||||
---
|
||||
|
||||
## v2.1.0 2018-10-01
|
||||
## Upcoming version 2019-XX-XX
|
||||
|
||||
* [change] Improve performance of public pages. Image assets are optimized and lazy loading is
|
||||
## [v3.5.1] 2019-09-16
|
||||
|
||||
- [add] add orverriding function `onAdd` and `onRemove` for `CustomOverlayView` in
|
||||
`SearchMapWithGoogleMap` to abide to React rules and do not `unmountComponentAtNode` when a
|
||||
component is rendered by React and use `appendChild` on `onAdd` instead of `draw` to
|
||||
[improve performance](https://github.com/tomchentw/react-google-maps/issues/817).
|
||||
[#1200](https://github.com/sharetribe/flex-template-web/pull/1200)
|
||||
- [fix] fix `CustomOverlayView` in `SearchMapWithGoogleMap` to work with new `react-intl` version,
|
||||
overriding `render` method to render child object by using `createPortal` instead of
|
||||
`unstable_renderSubtreeIntoContainer`.
|
||||
[#1200](https://github.com/sharetribe/flex-template-web/pull/1200)
|
||||
|
||||
## [v3.5.0] 2019-08-29
|
||||
|
||||
- [change] Change the design of `BookingBreakdown` and add options to show only dates or booking
|
||||
date and time there. [#1195](https://github.com/sharetribe/flex-template-web/pull/1195)
|
||||
- [change] Move `BookingTimeInfo` to separate component from `InboxPage`. Add options to show only
|
||||
booking dates or booking dates and times.
|
||||
[#1194](https://github.com/sharetribe/flex-template-web/pull/1194)
|
||||
- [add] Add new Spanish translations related to storing payment card.
|
||||
[#1193](https://github.com/sharetribe/flex-template-web/pull/1193)
|
||||
- [fix] Update yarn.lock (there was Lodash version resolution missing)
|
||||
[#1190](https://github.com/sharetribe/flex-template-web/pull/1190)
|
||||
|
||||
[v3.5.0]: https://github.com/sharetribe/flex-template-web/compare/v3.4.0...v3.5.0
|
||||
|
||||
## [v3.4.0] 2019-08-29
|
||||
|
||||
- [change] Update `react-intl` to 3.1.13. More information about the changes can be found from
|
||||
[Upgrade guide for react-intl@3.x](https://github.com/formatjs/react-intl/blob/master/docs/Upgrade-Guide.md)
|
||||
|
||||
- Proptype `intlShape` was removed so we needed to create it again. Because of this we added a new
|
||||
`util/reactIntl.js` file. This file is now used to wrap all the react-intl related imports.
|
||||
- `addLocaleDate` function was removed and react-intl library is now relying on native Intl APIs:
|
||||
[Intl.PluralRules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/PluralRules)
|
||||
and
|
||||
[Intl.RelativeTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RelativeTimeFormat).
|
||||
In order to support older browsers we needed to add `intl-pluralrules` and
|
||||
`intl-relativetimeformat` to `util/polyfills.js`
|
||||
- Also Node must be now compiled with `full-icu` which caused changes to `start` and `test`
|
||||
scripts in `package.json`. We also needed to add a specific config for `nodemon`
|
||||
- Default `textComponent`in `IntlProvider` changed to `React.Fragment` so we need to explicitly
|
||||
set `textComponent` to `span`. Otherwise all the snapshots would have changed and it might
|
||||
affect to UI if there is styles added to these spans generally in customization projects.
|
||||
|
||||
Note: `FormattedMessage` component now supports
|
||||
[`tagName` prop](https://github.com/formatjs/react-intl/blob/master/docs/Components.md#formattedmessage)
|
||||
and
|
||||
[improved rich-text formatting](https://github.com/formatjs/react-intl/blob/master/docs/Components.md#rich-text-formatting).
|
||||
[#1181](https://github.com/sharetribe/flex-template-web/pull/1181)
|
||||
|
||||
- [change] Update helmet (v3.20.0 > v3.20.1).
|
||||
[#1186](https://github.com/sharetribe/flex-template-web/pull/1186)
|
||||
- [fix] Lodash vulnerability: enforce newer version for react-google-maps and react-dates
|
||||
[#1188](https://github.com/sharetribe/flex-template-web/pull/1188)
|
||||
- [change] Update `React`, `react-test-renderer` and `react-dom` to 16.9.0. After these updates old
|
||||
lifecycle methods `componentWillMount`, `componentWillUpdate` and `componentWillUpdate` will cause
|
||||
deprecation warnings. Check the updated components from the PR
|
||||
[#1172](https://github.com/sharetribe/flex-template-web/pull/1172)
|
||||
- [fix] ProfileSettingsForm: clear correct timeout.
|
||||
[#1185](https://github.com/sharetribe/flex-template-web/pull/1185)
|
||||
- [fix] `availabilityPlan` prop in `EditListingAvailabilityForm` was missing.
|
||||
[#1183](https://github.com/sharetribe/flex-template-web/pull/1183)
|
||||
- [fix] Bug fix: valueFromForm prop wasn't passed through different subcomponents.
|
||||
[#1182](https://github.com/sharetribe/flex-template-web/pull/1182)
|
||||
- [add] Update German and French translations.
|
||||
[#1184](https://github.com/sharetribe/flex-template-web/pull/1184)
|
||||
- [change] Migrate from `react-helmet` to `react-helmet-async`
|
||||
[#1179](https://github.com/sharetribe/flex-template-web/pull/1179)
|
||||
- [change] Use `sanitize.css` from own file instead of npm package because updating it accidentally
|
||||
might break the UI. [#1177](https://github.com/sharetribe/flex-template-web/pull/1177)
|
||||
- [fix] Change app.test.js after `react-redux` update
|
||||
[#1178](https://github.com/sharetribe/flex-template-web/pull/1178)
|
||||
- [change] Update `react-redux`: v5.1.1 -> v7.1.1
|
||||
[#1176](https://github.com/sharetribe/flex-template-web/pull/1176)
|
||||
- [change] Update `seedrandom` from v2.4.4 to v3.0.3
|
||||
[#1175](https://github.com/sharetribe/flex-template-web/pull/1175)
|
||||
- [change] Update `inquirer` from v6.5.0 to v7.0.0
|
||||
[#1174](https://github.com/sharetribe/flex-template-web/pull/1174)
|
||||
- [change] Update final-form, final-form-arrays, react-final-form and react-final-form-arrays. This
|
||||
forced to make some code changes:
|
||||
|
||||
- Old recommendation of by-passing default field formatting or parsin isn't accepted anymore
|
||||
- `format={null}` => use identity function instead: `format={v => v}`
|
||||
- `parse={null}` => use identity function instead: `parse={v => v}`
|
||||
- Final Form passes input props (name, value, onChange, onBlur, etc. ) grouped inside input key
|
||||
- those props now include `type` attribute too.
|
||||
- We had old form naming pattern with prop 'form', which now conflicted with updated Final Form
|
||||
(The 'form' prop was used when Redux-Form was the form library)
|
||||
|
||||
[#1173](https://github.com/sharetribe/flex-template-web/pull/1173)
|
||||
|
||||
- [change] Update `react-dates` from v18.5.0 to v20.3.0
|
||||
[#1171](https://github.com/sharetribe/flex-template-web/pull/1171)
|
||||
- [change] Update Prettier to v1.18.2
|
||||
[#1170](https://github.com/sharetribe/flex-template-web/pull/1170)
|
||||
- [change] Update `path-to-regexp` to v3.0.0
|
||||
[#1169](https://github.com/sharetribe/flex-template-web/pull/1169)
|
||||
- [change] Update `sharetribe-scripts` to v3.1.1
|
||||
[#1167](https://github.com/sharetribe/flex-template-web/pull/1167)
|
||||
- [fix] Small change to remove card tect on `SavedCardDetails` modal.
|
||||
[#1166](https://github.com/sharetribe/flex-template-web/pull/1166)
|
||||
- [change] Update Sentry (@sentry/browser / @sentry/node) from v4.5.1 to v5.6.2
|
||||
[#1164](https://github.com/sharetribe/flex-template-web/pull/1164)
|
||||
- Update dependecies: all the easily updateable minor and batch updates: array.prototype.find,
|
||||
babel-jest, core-js, enzyme (et al.), express, helmet, inquirer, lodash, nodemon, raf, redux,
|
||||
source-map-support [#1163](https://github.com/sharetribe/flex-template-web/pull/1163)
|
||||
|
||||
[v3.4.0]: https://github.com/sharetribe/flex-template-web/compare/v3.3.0...v3.4.0
|
||||
|
||||
## [v3.3.0] 2019-08-22
|
||||
|
||||
- [add] Saving payment card after payment or without initial payment. This release contains quite a
|
||||
lot changes to many files. This includes:
|
||||
|
||||
- UI changes to `CheckoutPage` for showing the saved payment method
|
||||
- One more step to `handlePaymentIntent` flow on `CheckoutPage` if the user decides to save the
|
||||
payment card
|
||||
- Showing error notification on `TransactionPage` if saving the payment method has failed
|
||||
- Use Flex SDK v1.5.0 which has new endpoints for creating Stripe Customer and using Stripe
|
||||
SetupIntents
|
||||
- Add `handleCardSetup` function to `stripe.duck.js`
|
||||
- New shared duck file `paymentMethods.duck.js` for handling saving, deleting and replacing the
|
||||
payment method
|
||||
- New page `PaymentMethodsPage` in user's account settings
|
||||
- `StripePaymenAddress` used in `StripePaymentForm` is now a separate component used also in new
|
||||
`PaymentMethodsForm`
|
||||
- New `LayoutWrapperAccountSettingsSideNav` component which is used in account settings pages:
|
||||
`ContactDetailsPage`, `PasswordChangePage`, `PayoutPreferencesPage`, `PaymentMethodsPage`
|
||||
|
||||
[#1138](https://github.com/sharetribe/flex-template-web/pull/1138)
|
||||
|
||||
Read more from Flex docs:
|
||||
[How saving payment card works in FTW](https://www.sharetribe.com/docs/background/save-payment-card/)
|
||||
|
||||
[v3.3.0]: https://github.com/sharetribe/flex-template-web/compare/v3.2.1...v3.3.0
|
||||
|
||||
## [v3.2.1] 2019-08-22
|
||||
|
||||
- [fix] On `ListingPage` align avatar with the left side of the content and fix content width so
|
||||
that it aligns with the header image.
|
||||
[#1155](https://github.com/sharetribe/flex-template-web/pull/1155)
|
||||
- [fix] Rehydrate bug: existing DOM elements were populated incorrectly
|
||||
[#1154](https://github.com/sharetribe/flex-template-web/pull/1154)
|
||||
- [fix] Don't send personal id number or business profile to Stripe API when creating a Stripe
|
||||
customer if they are not required in `stripe-config.js`. This happened e.g. if someone filled the
|
||||
form after selecting the US and then before sending changed the country to Finland.
|
||||
[#1151](https://github.com/sharetribe/flex-template-web/pull/1151)
|
||||
- [add] Add new French and Spanish translations related to keyword search and Spanish translations
|
||||
related to payment intents. [#1148](https://github.com/sharetribe/flex-template-web/pull/1148)
|
||||
- [add] Add new French translations related to payment intents. Also few small changes to en.json
|
||||
for consistency. [#1139](https://github.com/sharetribe/flex-template-web/pull/1139)
|
||||
|
||||
[v3.2.1]: https://github.com/sharetribe/flex-template-web/compare/v3.2.0...v3.2.1
|
||||
|
||||
## [v3.2.0] 2019-07-08
|
||||
|
||||
- [add] Keyword search/filter added to SearchPage component.
|
||||
[#1129](https://github.com/sharetribe/flex-template-web/pull/1129)
|
||||
- [fix] temporarily remove audit CI job.
|
||||
[#1136](https://github.com/sharetribe/flex-template-web/pull/1136)
|
||||
- [change] Update outdated dependencies. This includes updating lodash to fix the security issue.
|
||||
[#1135](https://github.com/sharetribe/flex-template-web/pull/1135)
|
||||
|
||||
[v3.2.0]: https://github.com/sharetribe/flex-template-web/compare/v3.1.1...v3.2.0
|
||||
|
||||
## [v3.1.1] 2019-07-08
|
||||
|
||||
- [fix] Ensure on `TransactionPanel` that enquiry has a correct transition when a customer tries to
|
||||
book the listing. This might happen with transaction process changes (e.g. when changing from
|
||||
previous default to SCA process).
|
||||
[#1131](https://github.com/sharetribe/flex-template-web/pull/1131)
|
||||
|
||||
[v3.1.1]: https://github.com/sharetribe/flex-template-web/compare/v3.1.0...v3.1.1
|
||||
|
||||
## [v3.1.0] 2019-07-05
|
||||
|
||||
- [fix] SectionHero: fix type in search params. There was an extra "/s?".
|
||||
[#1124](https://github.com/sharetribe/flex-template-web/pull/1124)
|
||||
- [add] Add support for Singapore as the payout country of a provider. Also fix a bug in passing the
|
||||
personal ID number to Stripe. [#1122](https://github.com/sharetribe/flex-template-web/pull/1122)
|
||||
- [add] Add events.mapbox.com to `connect-src` in `csp.js` file.
|
||||
[#1123](https://github.com/sharetribe/flex-template-web/pull/1123)
|
||||
- [change] Verify email automatically once the verification link is clicked. Redirect the user to
|
||||
the landing page after verification.
|
||||
[#1121](https://github.com/sharetribe/flex-template-web/pull/1121)
|
||||
|
||||
[v3.0.0]: https://github.com/sharetribe/flex-template-web/compare/v3.0.0...v3.1.0
|
||||
|
||||
## [v3.0.0] 2019-07-02
|
||||
|
||||
- [add] Strong Customer Authentication (SCA) with Stripe's new PaymentIntents flow. This is a big
|
||||
change for checkout flow and includes a madatory transaction process change.
|
||||
[#1089](https://github.com/sharetribe/flex-template-web/pull/1089)
|
||||
|
||||
- You should check [the pull request](https://github.com/sharetribe/flex-template-web/pull/1089)
|
||||
- and read 3 Flex Docs articles:
|
||||
[SCA](https://www.sharetribe.com/docs/background/strong-customer-authentication/),
|
||||
[PaymentIntents](https://www.sharetribe.com/docs/background/payment-intents/), and
|
||||
[How to take PaymentIntents into use](https://www.sharetribe.com/docs/guide/how-to-take-payment-intents-into-use/)
|
||||
|
||||
[v3.0.0]: https://github.com/sharetribe/flex-template-web/compare/v2.17.1...v3.0.0
|
||||
|
||||
## [v2.17.1] 2019-06-11
|
||||
|
||||
- [fix] `stripeCardToken` didn't update when the user tried to book the same listing for a second
|
||||
time. This update will clear the old cardtoken from Redux store when redirecting to
|
||||
`TransactionPage`. [#1114](https://github.com/sharetribe/flex-template-web/pull/1114)
|
||||
- [fix] In `LineItemProviderCommissionMaybe.js` file check that `providerCommissionLineItem` exists.
|
||||
In default transaction process the `providerCommissionLineItem` can be expected to be there but if
|
||||
the process is using only customer commission there will be error.
|
||||
[#1112](https://github.com/sharetribe/flex-template-web/pull/1112)
|
||||
- [security] Update Flex SDK version to v1.4.1. The new version updates depencencies with security
|
||||
issues [#1111](https://github.com/sharetribe/flex-template-web/pull/1111)
|
||||
- [fix] Fix a bug in showing review links. Because of the bug the second review link was not visible
|
||||
in `ActivityFeed`. [#1106](https://github.com/sharetribe/flex-template-web/pull/1106)
|
||||
- [fix] Emptying the priceFilter component in the searchPage caused a page breaking error.
|
||||
[#1101](https://github.com/sharetribe/flex-template-web/pull/1101)
|
||||
|
||||
[v2.17.1]: https://github.com/sharetribe/flex-template-web/compare/v2.17.0...v2.17.1
|
||||
|
||||
## [v2.17.0] 2019-05-23
|
||||
|
||||
- [change] Mapbox library dependencies updated to v1.0.0.
|
||||
[#1099](https://github.com/sharetribe/flex-template-web/pull/1099)
|
||||
- Note: Mapbox changed their pricing scheme!
|
||||
- [fix] missing provider information (like SSN in US), might cause payment to fail on
|
||||
`CheckoutPage`. This improves related error message.
|
||||
[#1098](https://github.com/sharetribe/flex-template-web/pull/1098)
|
||||
- [fix] Menu needs to wait for mounting to calculate dimensions properly.
|
||||
[#1096](https://github.com/sharetribe/flex-template-web/pull/1096)
|
||||
- [fix] Renamed Component.example.css files to ComponentExample.css to fix bug introduced in one of
|
||||
the library updates. [#1095](https://github.com/sharetribe/flex-template-web/pull/1095)
|
||||
- [add] `rawOnly` flag for Styleguide examples using fixed positioning or full-page dimensions.
|
||||
[#1094](https://github.com/sharetribe/flex-template-web/pull/1094)
|
||||
- [fix] Show error when typing credit card number if e.g. the number is invalid. Fixes bug that was
|
||||
introduced in PR #1088. [#1092](https://github.com/sharetribe/flex-template-web/pull/1092)
|
||||
- [change] Use Final Form on `StripePaymentForm` for consistency. Note that card form Stripe
|
||||
Elements in `StripePaymentForm` is not a Final Form field so it's not available trough Final Form
|
||||
but handled separately. [#1088](https://github.com/sharetribe/flex-template-web/pull/1088)
|
||||
- [change] Move Stripe SDK call from `StripePaymentForm` to `stripe.duck.js` for consistency.
|
||||
[#1086](https://github.com/sharetribe/flex-template-web/pull/1086)
|
||||
|
||||
[v2.17.0]: https://github.com/sharetribe/flex-template-web/compare/v2.16.0...v2.17.0
|
||||
|
||||
## [v2.16.0] 2019-05-08
|
||||
|
||||
This release makes 2 big updates to `sharetribe-scripts` package (which is our fork from Create
|
||||
React App). It is updated from v1.1.5 ->
|
||||
[2.1.8](https://github.com/sharetribe/create-react-app/blob/master/CHANGELOG-2.x.md#migrating-from-1x-to-203)
|
||||
-> [3.0.0](https://github.com/sharetribe/create-react-app/blob/master/CHANGELOG.md). This brought up
|
||||
a couple of changes:
|
||||
|
||||
- package.json has now a **"browserlist"** configuration key. This gives you an option to affect
|
||||
browser support (it affects CSS Autoprefixer and JS build output).
|
||||
[You might want to update it.](https://github.com/sharetribe/flex-template-web/pull/1073)
|
||||
- IE support is removed from Create React App, but you can add polyfills yourself if needed.
|
||||
- React was updated to a version that supports _Hooks_ and _Rules of React_ eslint plugin is
|
||||
included.
|
||||
- All the npm vulnerability report exceptions were removed from `.auditrc`
|
||||
|
||||
There was also a couple of bug fixes you should check carefully:
|
||||
[#1082](https://github.com/sharetribe/flex-template-web/pull/1082),
|
||||
[#1084](https://github.com/sharetribe/flex-template-web/pull/1084).
|
||||
|
||||
**Changes:**
|
||||
|
||||
- [fix] Previous change from `currentUser.attributes.stripeConnected` to separately included
|
||||
`stripeAccount` caused errors since updates to currentUser entity didn't include `stripeAccount`.
|
||||
Including it every time sounds quite error-prone, so we reversed that change.
|
||||
[#1084](https://github.com/sharetribe/flex-template-web/pull/1084)
|
||||
- [fix] Edit `updatedEntities` function in `util/data.js` so that it doesn't mutate the
|
||||
`oldEntities` argument. [#1079](https://github.com/sharetribe/flex-template-web/pull/1079)
|
||||
- [change] Update sharetribe-scripts (CRA fork) to v3.0.0. There are a couple of changes that you
|
||||
should check from [#1081](https://github.com/sharetribe/flex-template-web/pull/1081)
|
||||
- Reserve use\* function naming pattern for React Hooks.
|
||||
- Recent SDK update changed the proptypes for snapshots.
|
||||
- Updated scripts/config.js after Prettier version bump
|
||||
- Removed unnecessary audit exceptions
|
||||
- [fix] Ensure on `TransactionPage` that all the required data is loaded before showing the page.
|
||||
[#1082](https://github.com/sharetribe/flex-template-web/pull/1082)
|
||||
- [fix] Use proper method for Sentry on logout to avoid error message.
|
||||
[#1080](https://github.com/sharetribe/flex-template-web/pull/1080),
|
||||
- [change] Update sharetribe-scripts (CRA fork) to v2.1.8. There are a couple of changes that you
|
||||
should check from [#1073](https://github.com/sharetribe/flex-template-web/pull/1073)
|
||||
- package.json has now a "browserlist" configuration key. This gives you an option to affect
|
||||
Autoprefixer configs (aka CSS vendor prefixes / browser support). You might want to update it.
|
||||
- IE support is removed from Create React App, but you can add polyfills yourself if needed.
|
||||
- Test snapshots were also changed a bit. (Update your own custom tests if needed.)
|
||||
- Some CSS and React rules were more strict, you might need to make changes to your custom code.
|
||||
- [fix] New npm vulnerability alerts checked and added to exception list.
|
||||
[#1075](https://github.com/sharetribe/flex-template-web/pull/1075)
|
||||
- [fix] ListingPage.duck: fix minor bug on dispatching the fetchReviewsRequest action
|
||||
[#1074](https://github.com/sharetribe/flex-template-web/pull/1074)
|
||||
|
||||
[v2.16.0]: https://github.com/sharetribe/flex-template-web/compare/v2.15.0...v2.16.0
|
||||
|
||||
## [v2.15.0] 2019-04-24
|
||||
|
||||
- [add] Improve printing API errors on web inspector (console.table)
|
||||
[#1071](https://github.com/sharetribe/flex-template-web/pull/1071)
|
||||
- [fix] ManageAvailabilityCalendar.js didn't use UTC time when fetching data for calendar months.
|
||||
[#1069](https://github.com/sharetribe/flex-template-web/pull/1069)
|
||||
- [add] Use sparse fields on InboxPage query to reduce data load.
|
||||
[#1067](https://github.com/sharetribe/flex-template-web/pull/1067)
|
||||
- NOTE: if you need more fields on `InboxPage`, you need to add those to `loadData` function.
|
||||
- [add] Use sparse fields on SearchPage to reduce data load.
|
||||
[#1066](https://github.com/sharetribe/flex-template-web/pull/1066)
|
||||
|
||||
- NOTE: if you need more fields on `ListingCard` than title, price and geolocation - you need to
|
||||
add those to `loadData` function.
|
||||
|
||||
[v2.15.0]: https://github.com/sharetribe/flex-template-web/compare/v2.14.0...v2.15.0
|
||||
|
||||
## [v2.14.0] 2019-04-05
|
||||
|
||||
- [add] German translations for recent PayoutDetailsForm changes.
|
||||
[#1064](https://github.com/sharetribe/flex-template-web/pull/1064)
|
||||
- [add] Added NZD and HKD subunit divisors and refactored currency configuration.
|
||||
[#1063](https://github.com/sharetribe/flex-template-web/pull/1063)
|
||||
- [add] Add support for arbitrary line items.
|
||||
[#1062](https://github.com/sharetribe/flex-template-web/pull/1062)
|
||||
- [fix] US individual accounts had a non-editable business url in PayoutDetailsForm. It was probably
|
||||
OK, but there wasn't any reason to for it.
|
||||
[#1061](https://github.com/sharetribe/flex-template-web/pull/1061)
|
||||
|
||||
[v2.14.0]: https://github.com/sharetribe/flex-template-web/compare/v2.13.1...v2.14.0
|
||||
|
||||
## [v2.13.1] 2019-03-29
|
||||
|
||||
- [add] a comment about category and amenities filters. They don't work out-of-the-box, extended
|
||||
data needs a schema before it can work as a search filter.
|
||||
[#1055](https://github.com/sharetribe/flex-template-web/pull/1055)
|
||||
- [fix] EditListingWizard: currentUser was null when the EditListingPage got reloaded causing
|
||||
TypeError. [#1056](https://github.com/sharetribe/flex-template-web/pull/1056)
|
||||
|
||||
[v2.13.1]: https://github.com/sharetribe/flex-template-web/compare/v2.13.0...v2.13.1
|
||||
|
||||
## [v2.13.0] 2019-03-28
|
||||
|
||||
- [add] Add translations for recent Stripe API related changes. (German will be included later.)
|
||||
[#1052](https://github.com/sharetribe/flex-template-web/pull/1052)
|
||||
- [fix] JPY currency was configured wrongly: it doesn't use subunits.
|
||||
[#1051](https://github.com/sharetribe/flex-template-web/pull/1051)
|
||||
- [add] Complete rewrite to `PayoutDetailsForm` due to breaking changes in Stripe API.
|
||||
[#1049](https://github.com/sharetribe/flex-template-web/pull/1049)
|
||||
- You should track all your customizations to `PayoutDetailsForm` and related changes in
|
||||
`user.duck.js` and elsewhere before merging this upstream-update.
|
||||
- You should update Stripe API to "2019-02-19" or later
|
||||
- [add] Booking: use attributes `displayStart` and `displayEnd`, instead of reading booking period
|
||||
directly from `start` and `end` attributes.
|
||||
[#1050](https://github.com/sharetribe/flex-template-web/pull/1050)
|
||||
- [fix] A listing title that contained only stripped-off characters caused bugs in slug / pathName
|
||||
generation. [#1048](https://github.com/sharetribe/flex-template-web/pull/1048)
|
||||
- [change] Removed Node-engine setup from package.json. Fixed version was causing problems for quite
|
||||
many in their first FTW installation. Note: when troubleshooting your Heroku installation, you
|
||||
might want to reintroduce engine setup.
|
||||
[#1043](https://github.com/sharetribe/flex-template-web/pull/1043)
|
||||
- [fix] Add error handling to `PayoutDetailsForm` and `StripePaymentForm` in case Stripe publishable
|
||||
key is not configured yet. [#1042](https://github.com/sharetribe/flex-template-web/pull/1042)
|
||||
- [fix] FieldBirthdayInput: placeholder text was not selected by default.
|
||||
[#1039](https://github.com/sharetribe/flex-template-web/pull/1039)
|
||||
|
||||
[v2.13.0]: https://github.com/sharetribe/flex-template-web/compare/v2.12.1...v2.13.0
|
||||
|
||||
## [v2.12.0] 2019-02-28
|
||||
|
||||
- [fix] Fix to PR [#1035](https://github.com/sharetribe/flex-template-web/pull/1035). In
|
||||
`user.duck.js` send correct params depending on Stripe API in use.
|
||||
[#1037](https://github.com/sharetribe/flex-template-web/pull/1037)
|
||||
- [change] Update creating Stripe account token to support the latest Stripe API update. See also
|
||||
[Stripe API changelog](https://stripe.com/docs/upgrades#api-changelog). **IMPORTANT:** If you are
|
||||
using a Stripe account created earlier than 19th of February 2019 you need to change the value of
|
||||
`useDeprecatedLegalEntityWithStripe` in `stripe-config.js`. You can check the Stripe API version
|
||||
you are using from Stripe Dashboard -> Developers. Since the change in Stripe API was quite big we
|
||||
are not able to support company accounts with new Stripe API yet! The option for company accounts
|
||||
will be hidden if the value `useDeprecatedLegalEntityWithStripe` is set to `false`.
|
||||
[#1035](https://github.com/sharetribe/flex-template-web/pull/1035)
|
||||
- [change] Improve German translations.
|
||||
[#1034](https://github.com/sharetribe/flex-template-web/pull/1034)
|
||||
- [change] Reordered import/exports on src/components/index.js. This helps to mitigate possible
|
||||
circular dependency problems and strange bugs in CSS bundle. In addition, derivative buttons were
|
||||
refactored to work with `rootClassName` prop (PrimaryButton, SecondaryButton and
|
||||
InlineTextButton). [#1024](https://github.com/sharetribe/flex-template-web/pull/1024)
|
||||
|
||||
[v2.12.0]: https://github.com/sharetribe/flex-template-web/compare/v2.11.1...v2.12.0
|
||||
|
||||
## [v2.11.1] 2019-02-21
|
||||
|
||||
- [add] New translations for French and Spanish (fr.json & es.json)
|
||||
[#1028](https://github.com/sharetribe/flex-template-web/pull/1028)
|
||||
- [add] New translation file German (de.json). This also adds hyphenation to some of the titles.
|
||||
[#1027](https://github.com/sharetribe/flex-template-web/pull/1027)
|
||||
|
||||
[v2.11.0]: https://github.com/sharetribe/flex-template-web/compare/v2.11.0...v2.11.1
|
||||
|
||||
## [v2.11.0] 2019-02-20
|
||||
|
||||
- [fix] SelectMultipleFilter had a bug on mobile layout - `onSubmit` didn't get called. This fixes
|
||||
also two other issues with SelectMultipleFilter: hovering on ListingCard removed dirty values on
|
||||
repaint and there was an outline flashing on FilterForm when clicking checkboxes.
|
||||
[#1025](https://github.com/sharetribe/flex-template-web/pull/1025)
|
||||
- [fix] Small changes in CSS files in order to match content width with the footer in pages where
|
||||
the footer is visible. Also, make side layout (used e.g. in `TermsOfServicePage`, `InboxPage`,
|
||||
`ContactDetailsPage`) align width footer better. Check responsive layouts carefulle after taking
|
||||
update from upstream. [#1090](https://github.com/sharetribe/flex-template-web/pull/1019)
|
||||
- [add] This adds an example how user-generated content could be sanitized. If you have extended
|
||||
data you should consider if sanitization is needed for that.
|
||||
[#1023](https://github.com/sharetribe/flex-template-web/pull/1023)
|
||||
- [change] A new component `UserDisplayName` is added for showing user display name and also
|
||||
handling the cases where a user is banned or deleted. When the user name must be a string instead
|
||||
of a component (e.g. in `Avatar` and in `ListingPage`) you can use a new function
|
||||
`userDisplayNameAsString`. Together these will replace the old `userDisplayName` function which is
|
||||
now deprecated. Also some small bug fixes to showing banned user. There is quite a lot of file
|
||||
changes caused by updating test files.
|
||||
[#1022](https://github.com/sharetribe/flex-template-web/pull/1022)
|
||||
- [change] Remove error handling for unverified email from PasswordRecoveryPage and translations
|
||||
related to that. [#1021](https://github.com/sharetribe/flex-template-web/pull/1021)
|
||||
|
||||
[v2.11.0]: https://github.com/sharetribe/flex-template-web/compare/v2.10.0...v2.11.0
|
||||
|
||||
## [v2.10.0] 2019-01-31
|
||||
|
||||
- [add] Add audit script and include it as a CI job. We had security audit job previously on top of
|
||||
node security platform (nsp), but that service was closed on December 2018.
|
||||
[#1020](https://github.com/sharetribe/flex-template-web/pull/1020)
|
||||
- [change] Extracted and refactored utility functions related to transaction and refactored several
|
||||
components that show transaction data (incl. InboxPage, TransactionPanel, ActivityFeed). Before
|
||||
updating your customization project, you should read more about what has changed from the pull
|
||||
request. [#1004](https://github.com/sharetribe/flex-template-web/pull/1004)
|
||||
- [change] Rest of the documentation moved to Flex Docs: https://www.sharetribe.com/docs/
|
||||
[#1015](https://github.com/sharetribe/flex-template-web/pull/1015)
|
||||
|
||||
[v2.10.0]: https://github.com/sharetribe/flex-template-web/compare/v2.9.0...v2.10.0
|
||||
|
||||
## [v2.9.0] 2019-01-29
|
||||
|
||||
- [fix] day boundaries for date filter and pass booking state to bookings.query
|
||||
- SearchPage.duck.js: endDate should not be expanded for night bookings
|
||||
- DateRangeController: bookingUnitType: day should allow 0 night
|
||||
- EditListingPage.duck.js booking state should be passed to query
|
||||
[#1016](https://github.com/sharetribe/flex-template-web/pull/1016)
|
||||
- [add] Date filter added and filter components (single and multiselect) are refactored to use
|
||||
shared subcomponents. [#949](https://github.com/sharetribe/flex-template-web/pull/949)
|
||||
- [fix] Fixed copy-text in ReviewForm: Rating is required.
|
||||
[#1011](https://github.com/sharetribe/flex-template-web/pull/1011)
|
||||
- [change] Some of the documentation moved to Flex Docs: https://www.sharetribe.com/docs/
|
||||
[#1012](https://github.com/sharetribe/flex-template-web/pull/1012) and
|
||||
[#1014](https://github.com/sharetribe/flex-template-web/pull/1014)
|
||||
- [fix] Allow ownListing as listing proptype in BookingPanel component.
|
||||
[#1007](https://github.com/sharetribe/flex-template-web/pull/1007)
|
||||
- [add] Add info text about additional owners to `PayoutDetailsForm`.
|
||||
[#1006](https://github.com/sharetribe/flex-template-web/pull/1006)
|
||||
- [change] Default to English translation if the translation key is missing. After this update, new
|
||||
translation keys will not be added to other translation files with English default texts. We keep
|
||||
providing translations in our supported languages but they might not be up to date all the time.
|
||||
This means if you want to update your translations beforehand or use your own translations file,
|
||||
you can use
|
||||
[translation CLI](https://github.com/sharetribe/flex-template-web/blob/master/docs/translations.md#managing-translations)
|
||||
to check if there are translations missing.
|
||||
[#1005](https://github.com/sharetribe/flex-template-web/pull/1005)
|
||||
- [change] Remove `origin` parameter from `default-location-searches.js`
|
||||
[#1003](https://github.com/sharetribe/flex-template-web/pull/1003)
|
||||
- [add] Limit location autocomplete by adding an optional country parameter to geocoding call in
|
||||
both Mapbox and Google Maps integrations. Also updated Mapbox SDK to version 0.5.0.
|
||||
[#1002](https://github.com/sharetribe/flex-template-web/pull/1002)
|
||||
|
||||
[v2.9.0]: https://github.com/sharetribe/flex-template-web/compare/v2.8.0...v2.9.0
|
||||
|
||||
## [v2.8.0] 2019-01-17
|
||||
|
||||
- [add] Add CLI tool for creating .env file and setting up the environment variables.
|
||||
[#994](https://github.com/sharetribe/flex-template-web/pull/994)
|
||||
- [change] Change from Raven to Sentry SDKs for browser and Node.js to version 4.5.1. With the new
|
||||
SDKs only one DSN needs to be configured so update also environment variables and documentation
|
||||
related to Sentry. [#999](https://github.com/sharetribe/flex-template-web/pull/999)
|
||||
- [fix] Use environment variable `REACT_APP_AVAILABILITY_ENABLED` to enable or disable availability
|
||||
calendar. In the config.js file variable fetchAvailableTimeSlots is now renamed to more general
|
||||
enableAvailability because it affects both fetching availability data and enabling the
|
||||
availability calendar. [#1000](https://github.com/sharetribe/flex-template-web/pull/1000)
|
||||
- [fix] UI was broken for banned user after transaction enquiry changes.
|
||||
[#996](https://github.com/sharetribe/flex-template-web/pull/996)
|
||||
|
||||
[v2.8.0]: https://github.com/sharetribe/flex-template-web/compare/v2.7.1...v2.8.0
|
||||
|
||||
## [v2.7.1] 2019-01-09
|
||||
|
||||
- [add] Separate date ranges when fetching availability exceptions and bookings on availability
|
||||
calendar. After this change, providers can block dates 365 days in advance instead of just 180
|
||||
days. [#997](https://github.com/sharetribe/flex-template-web/pull/997)
|
||||
- [fix] Fixed a small typo. [#995](https://github.com/sharetribe/flex-template-web/pull/995)
|
||||
|
||||
[v2.7.1]: https://github.com/sharetribe/flex-template-web/compare/v2.7.0...v2.7.1
|
||||
|
||||
## [v2.7.0] 2019-01-08
|
||||
|
||||
- [add] Add Spanish translations file: es.json and update docs/translations.md
|
||||
[#992](https://github.com/sharetribe/flex-template-web/pull/992)
|
||||
- [add] Add French translations to new translations keys. Few minor updates to English translations
|
||||
for consistency. [#991](https://github.com/sharetribe/flex-template-web/pull/991)
|
||||
- [add] Support for Stripe company accounts. `PayoutDetailsForm` was separated into smaller
|
||||
subcomponents. Multiple new translation keys were added and they might not be translated into
|
||||
French yet. [#980](https://github.com/sharetribe/flex-template-web/pull/980)
|
||||
- Manage availability of listings. This works for listings that have booking unit type:
|
||||
'line-item/night', or 'line-item/day'. There's also 'manage availability' link in the
|
||||
ManageListingCards of "your listings" page.
|
||||
[#972](https://github.com/sharetribe/flex-template-web/pull/972)
|
||||
|
||||
[v2.7.0]: https://github.com/sharetribe/flex-template-web/compare/v2.6.0...v2.7.0
|
||||
|
||||
## [v2.6.0] 2019-01-02
|
||||
|
||||
- [fix] Wrong translations for perUnit in fr.json.
|
||||
[#989](https://github.com/sharetribe/flex-template-web/pull/989)
|
||||
- [change] Layout changes to BookingPanel on listing and transaction pages.
|
||||
[#988](https://github.com/sharetribe/flex-template-web/pull/988)
|
||||
- [fix] Fix wrong booking title on listing page that has been introduced in #969.
|
||||
[#987](https://github.com/sharetribe/flex-template-web/pull/987)
|
||||
- [fix] yarn.lock file was not up to date
|
||||
[#986](https://github.com/sharetribe/flex-template-web/pull/986)
|
||||
- [add] Add an image of fork button to the deploy to production guide.
|
||||
[#985](https://github.com/sharetribe/flex-template-web/pull/985)
|
||||
- [remove] Remove the default built-in email templates. Built-in email templates can be edited in
|
||||
Console. [#983](https://github.com/sharetribe/flex-template-web/pull/983)
|
||||
- [add] Enable booking a listing straight from an enquiry
|
||||
[#976](https://github.com/sharetribe/flex-template-web/pull/976)
|
||||
- [change] Extract SectionBooking to a distinct component from ListingPage.
|
||||
[#969](https://github.com/sharetribe/flex-template-web/pull/969)
|
||||
|
||||
[v2.6.0]: https://github.com/sharetribe/flex-template-web/compare/v2.5.0...v2.6.0
|
||||
|
||||
## [v2.5.0] 2018-12-17
|
||||
|
||||
- [add] Add French translations to recently added translation keys. Also few minor changes to
|
||||
English translations for consistency.
|
||||
[#981](https://github.com/sharetribe/flex-template-web/pull/981)
|
||||
- [add] Create FieldRadioButton component.
|
||||
[#977](https://github.com/sharetribe/flex-template-web/pull/977)
|
||||
- [fix] Temporarily remove audit step from CI because of the Node Security Platform shutting down.
|
||||
[#979](https://github.com/sharetribe/flex-template-web/pull/979)
|
||||
- [add] Add Stripe support for new countries: Canada, New Zealand, Switzerland, Norway, and Hong
|
||||
Kong. Also add more required fields for US and Australia.
|
||||
- StripeBankAccountTokenInputField component and PayoutDetailsForm have some changes
|
||||
- Stripe related configuration is separated to new stripe-config.js file.
|
||||
- Multiple new translation keys were added and they might not be translated into French yet. If
|
||||
you use French translation check PR for the changed keys.
|
||||
[#968](https://github.com/sharetribe/flex-template-web/pull/968)
|
||||
- [change] Remove generic perUnit translations and replace them with specific night, day and unit
|
||||
translations depending on booking unit chosen in config.
|
||||
[#970](https://github.com/sharetribe/flex-template-web/pull/970)
|
||||
- [fix] Formatting docs with newest Prettier - related commit was lost in #967 at some point.
|
||||
[#975](https://github.com/sharetribe/flex-template-web/pull/975)
|
||||
- [change] Improved documents related to onboarding: env.md, deploying-to-production.md,
|
||||
map-providers.md [#971](https://github.com/sharetribe/flex-template-web/pull/971)
|
||||
- [change] Update outdated dependencies.
|
||||
[#967](https://github.com/sharetribe/flex-template-web/pull/967)
|
||||
- **Note:** Updating Prettier caused multiple file changes mostly to `.md` files and `compose`
|
||||
setup.
|
||||
- [change] Update supported Node version to the latest LTS (10.14).
|
||||
[#964](https://github.com/sharetribe/flex-template-web/pull/964)
|
||||
- [add] Add documentation about deploying to production. Also add _deploy to Heroku_ button.
|
||||
[#961](https://github.com/sharetribe/flex-template-web/pull/961)
|
||||
|
||||
[v2.5.0]: https://github.com/sharetribe/flex-template-web/compare/v2.4.1...v2.5.0
|
||||
|
||||
## [v2.4.1] 2018-11-29
|
||||
|
||||
- [fix] Add missing French translation.
|
||||
[#966](https://github.com/sharetribe/flex-template-web/pull/966)
|
||||
|
||||
[v2.4.1]: https://github.com/sharetribe/flex-template-web/compare/v2.4.0...v2.4.1
|
||||
|
||||
## [v2.4.0] 2018-11-28
|
||||
|
||||
- [change] Update Flex JS SDK to 1.2.
|
||||
[#963](https://github.com/sharetribe/flex-template-web/pull/963)
|
||||
- [add] Add French as a default language in addition to English.
|
||||
[#962](https://github.com/sharetribe/flex-template-web/pull/962)
|
||||
- [fix] Show Stripe error message on CheckoutPage if payment request fails because of Stripe error.
|
||||
Also show error if payment amount is zero.
|
||||
[#960](https://github.com/sharetribe/flex-template-web/pull/960)
|
||||
- [fix] Remove unused translation keys and update PasswordChangePage title
|
||||
[#959](https://github.com/sharetribe/flex-template-web/pull/959)
|
||||
- [add] Add translations CLI tool [#955](https://github.com/sharetribe/flex-template-web/pull/955)
|
||||
|
||||
[v2.4.0]: https://github.com/sharetribe/flex-template-web/compare/v2.3.2...v2.4.0
|
||||
|
||||
## [v2.3.2] 2018-11-20
|
||||
|
||||
- [fix] Take 2: don't set currentUserHasListings if fetched listing is in draft state.
|
||||
[#956](https://github.com/sharetribe/flex-template-web/pull/956)
|
||||
- [fix] PriceFilter styles [#954](https://github.com/sharetribe/flex-template-web/pull/954)
|
||||
|
||||
[v2.3.2]: https://github.com/sharetribe/flex-template-web/compare/v2.3.1...v2.3.2
|
||||
|
||||
## [v2.3.1] 2018-11-16
|
||||
|
||||
- [fix] Don't set currentUserHasListings if fetched listing is in draft state.
|
||||
ModalMissingInformation was shown too early for users creating their first listing.
|
||||
[#953](https://github.com/sharetribe/flex-template-web/pull/953)
|
||||
- [change] Add index files of components and containers folder to .prettierignore
|
||||
[#952](https://github.com/sharetribe/flex-template-web/pull/952)
|
||||
- [fix] the alignment of arrows in FieldDateRangeInput and refactoring arrow icon code.
|
||||
[#951](https://github.com/sharetribe/flex-template-web/pull/951)
|
||||
- [change] Remove unnecessary language configuration and improve translations documentation.
|
||||
[#950](https://github.com/sharetribe/flex-template-web/pull/950)
|
||||
|
||||
[v2.3.1]: https://github.com/sharetribe/flex-template-web/compare/v2.3.0...v2.3.1
|
||||
|
||||
## [v2.3.0] 2018-11-13
|
||||
|
||||
- [add] Draft listing is used in EditListingWizard, ManageListingCard and ListingPage. From now on
|
||||
description panel creates a draft listing and photos panel publishes it. You can also view your
|
||||
current draft listings from 'your listings' page.
|
||||
[#947](https://github.com/sharetribe/flex-template-web/pull/947)
|
||||
- [fix] Firefox showed select options with the same color as select itself. Now options have their
|
||||
own color set and _placeholder option needs to be disabled_.
|
||||
[#946](https://github.com/sharetribe/flex-template-web/pull/946)
|
||||
|
||||
[v2.3.0]: https://github.com/sharetribe/flex-template-web/compare/v2.2.0...v2.3.0
|
||||
|
||||
## [v2.2.0] 2018-10-31
|
||||
|
||||
- [add] SearchPage: adds PriceFilter (and RangeSlider, FieldRangeSlider, PriceFilterForm).
|
||||
|
||||
**Note:** You must define min and max for the filter in `src/marketplace-custom-config.js`.
|
||||
Current maximum value for the range is set to 1000 (USD/EUR/currency units). In addition, this
|
||||
fixes or removes component examples that don't work in StyleguidePage.
|
||||
|
||||
[#944](https://github.com/sharetribe/flex-template-web/pull/944)
|
||||
|
||||
[v2.2.0]: https://github.com/sharetribe/flex-template-web/compare/v2.1.1...v2.2.0
|
||||
|
||||
## [v2.1.1] 2018-10-23
|
||||
|
||||
- [add] Added initial documentation about routing and loading data.
|
||||
[#941](https://github.com/sharetribe/flex-template-web/pull/941)
|
||||
- [remove] Removed plain text parts of email templates.
|
||||
[#942](https://github.com/sharetribe/flex-template-web/pull/942)
|
||||
- [add] Add referrer policy due tokens in URL on PasswordResetPage and EmailVerificationPage.
|
||||
[#940](https://github.com/sharetribe/flex-template-web/pull/940)
|
||||
- [add] Added initial documentation about our Redux setup.
|
||||
[#939](https://github.com/sharetribe/flex-template-web/pull/939)
|
||||
- [add] Added a small comment to documentation about the current state of code-splitting.
|
||||
[#938](https://github.com/sharetribe/flex-template-web/pull/938)
|
||||
|
||||
[v2.1.1]: https://github.com/sharetribe/flex-template-web/compare/v2.1.0...v2.1.1
|
||||
|
||||
## [v2.1.0] 2018-10-01
|
||||
|
||||
- [change] Improve performance of public pages. Image assets are optimized and lazy loading is
|
||||
applied to images in SectionLocation and ListingCard. Read
|
||||
[documentation](./docs/improving-performance.md) for implementation details.
|
||||
[#936](https://github.com/sharetribe/flex-template-web/pull/936)
|
||||
* [change] Update sharetribe-scripts. **cssnext** (used previously in sharetribe-scripts) has been
|
||||
- [change] Update sharetribe-scripts. **cssnext** (used previously in sharetribe-scripts) has been
|
||||
deprecated. Now **postcss-preset-env** is used instead with stage 3 + custom media queries and
|
||||
nesting-rules. If this change breaks your styling, you could still use v1.1.2. The next version of
|
||||
[postcss-nesting](https://github.com/jonathantneal/postcss-nesting) (v7.0.0) will no longer
|
||||
support nested at-rules (like media queries) - therefore, we didn't update to that version yet.
|
||||
[#935](https://github.com/sharetribe/flex-template-web/pull/935)
|
||||
* [change] Change Mapbox's default font to marketplace font.
|
||||
- [change] Change Mapbox's default font to marketplace font.
|
||||
[#934](https://github.com/sharetribe/flex-template-web/pull/934)
|
||||
* [add] New default design for the landing page's hero section. Now the CTA button's default
|
||||
- [add] New default design for the landing page's hero section. Now the CTA button's default
|
||||
behavior is 'Browse'.
|
||||
* `marketplaceH1FontStyles`: changed letter spacing to be more tight.
|
||||
* `SectionHero` has now a search page link that should be customized to point to your marketplace
|
||||
- `marketplaceH1FontStyles`: changed letter spacing to be more tight.
|
||||
- `SectionHero` has now a search page link that should be customized to point to your marketplace
|
||||
primary area [#933](https://github.com/sharetribe/flex-template-web/pull/933)
|
||||
|
||||
## v2.0.0 2018-09-19
|
||||
[v2.1.0]: https://github.com/sharetribe/flex-template-web/compare/v2.0.0...v2.1.0
|
||||
|
||||
* [add] New default map provider (Mapbox) and complete refactoring to all map and geocoding
|
||||
## [v2.0.0] 2018-09-19
|
||||
|
||||
- [add] New default map provider (Mapbox) and complete refactoring to all map and geocoding
|
||||
components. [#888](https://github.com/sharetribe/flex-template-web/pull/888)
|
||||
|
||||
**Note:** Before updating to version 2.0.0, you should very carefully track customizations that
|
||||
you have made to following components:
|
||||
|
||||
* **LocationAutocompleteInput**
|
||||
* **Map**
|
||||
* **SearchPage** (especially previous `onIdle` function)
|
||||
* **SearchMap**
|
||||
* **SearchMapPriceLabel**
|
||||
* **SearchMapGroupLabel**
|
||||
* **SearchMapInfoCard**
|
||||
- **LocationAutocompleteInput**
|
||||
- **Map**
|
||||
- **SearchPage** (especially previous `onIdle` function)
|
||||
- **SearchMap**
|
||||
- **SearchMapPriceLabel**
|
||||
- **SearchMapGroupLabel**
|
||||
- **SearchMapInfoCard**
|
||||
|
||||
To get a better understanding of what has changed, you should read documents about how to
|
||||
[integrate to map providers](./docs/map-providers.md) and especially
|
||||
[changing map provider to Google Maps](./docs/google-maps.md)
|
||||
|
||||
## v1.4.3 2018-09-15
|
||||
[v2.0.0]: https://github.com/sharetribe/flex-template-web/compare/v1.4.3...v2.0.0
|
||||
|
||||
* [fix] fuzzy location didn't change when listing location changed.
|
||||
## [v1.4.3] 2018-09-15
|
||||
|
||||
- [fix] fuzzy location didn't change when listing location changed.
|
||||
[#931](https://github.com/sharetribe/flex-template-web/pull/931)
|
||||
* [fix] obfuscatedCoordinatesImpl didn't always return coordinates within given offset radius.
|
||||
- [fix] obfuscatedCoordinatesImpl didn't always return coordinates within given offset radius.
|
||||
[#930](https://github.com/sharetribe/flex-template-web/pull/930)
|
||||
* [fix] LocationAutocompleteInput: blur input when selecting by enter to prevent flash of default
|
||||
- [fix] LocationAutocompleteInput: blur input when selecting by enter to prevent flash of default
|
||||
predictions. [#928](https://github.com/sharetribe/flex-template-web/pull/928)
|
||||
* [fix] LocationAutocompleteInput: selecting with enter key prevented while fetching predictions.
|
||||
- [fix] LocationAutocompleteInput: selecting with enter key prevented while fetching predictions.
|
||||
[#923](https://github.com/sharetribe/flex-template-web/pull/923)
|
||||
|
||||
## v1.4.2 2018-09-06
|
||||
[v1.4.3]: https://github.com/sharetribe/flex-template-web/compare/v1.4.2...v1.4.3
|
||||
|
||||
* [add] Reduce character queries on LocationAutocompleteInput to reduce geocoding costs.
|
||||
## [v1.4.2] 2018-09-06
|
||||
|
||||
- [add] Reduce character queries on LocationAutocompleteInput to reduce geocoding costs.
|
||||
[#883](https://github.com/sharetribe/flex-template-web/pull/883)
|
||||
* [change] Update git links and improve documentation
|
||||
- [change] Update git links and improve documentation
|
||||
[#911](https://github.com/sharetribe/flex-template-web/pull/911)
|
||||
* [change] improve env-template to better defaults.
|
||||
- [change] improve env-template to better defaults.
|
||||
[#912](https://github.com/sharetribe/flex-template-web/pull/912)
|
||||
* [fix] Touch event from location autocomplete prediction list ended up causing clicks.
|
||||
- [fix] Touch event from location autocomplete prediction list ended up causing clicks.
|
||||
[#917](https://github.com/sharetribe/flex-template-web/pull/917)
|
||||
* [change] Disable default predictions in listing wizard
|
||||
- [change] Disable default predictions in listing wizard
|
||||
[#906](https://github.com/sharetribe/flex-template-web/pull/906)
|
||||
|
||||
## v1.4.1 2018-08-21
|
||||
[v1.4.2]: https://github.com/sharetribe/flex-template-web/compare/v1.4.1...v1.4.2
|
||||
|
||||
* [fix] Fix window resize redirecting to search page with reusable map component
|
||||
## [v1.4.1] 2018-08-21
|
||||
|
||||
- [fix] Fix window resize redirecting to search page with reusable map component
|
||||
[#905](https://github.com/sharetribe/flex-template-web/pull/905)
|
||||
|
||||
* [change] Maps configuration has been restructured. The new configuration is agnostic of the maps
|
||||
- [change] Maps configuration has been restructured. The new configuration is agnostic of the maps
|
||||
provider in use and works with both Google Maps as well as Mapbox.
|
||||
|
||||
The fuzzy location circle has less configuration, but otherwise all the previous settings can be
|
||||
|
|
@ -93,130 +751,154 @@ way to update this template, but currently, we follow a pattern:
|
|||
|
||||
[#900](https://github.com/sharetribe/flex-template-web/pull/900)
|
||||
|
||||
## v1.4.0 2018-08-17
|
||||
[v1.4.1]: https://github.com/sharetribe/flex-template-web/compare/v1.4.0...v1.4.1
|
||||
|
||||
* [change] Put availability calendar behind a feature flag
|
||||
## [v1.4.0] 2018-08-17
|
||||
|
||||
- [change] Put availability calendar behind a feature flag
|
||||
[#902](https://github.com/sharetribe/flex-template-web/pull/902)
|
||||
* [fix] Drop date time from time slots request query params
|
||||
- [fix] Drop date time from time slots request query params
|
||||
[#901](https://github.com/sharetribe/flex-template-web/pull/901)
|
||||
* [fix] Make a second time slots request when required
|
||||
- [fix] Make a second time slots request when required
|
||||
[#901](https://github.com/sharetribe/flex-template-web/pull/901)
|
||||
* [add] Map component (used in ListingPage) using Mapbox instead of Google Maps
|
||||
- [add] Map component (used in ListingPage) using Mapbox instead of Google Maps
|
||||
[#896](https://github.com/sharetribe/flex-template-web/pull/896)
|
||||
* [add] Listing availability [#868](https://github.com/sharetribe/flex-template-web/pull/868),
|
||||
- [add] Listing availability [#868](https://github.com/sharetribe/flex-template-web/pull/868),
|
||||
[#873](https://github.com/sharetribe/flex-template-web/pull/873),
|
||||
[#891](https://github.com/sharetribe/flex-template-web/pull/891) &
|
||||
[#892](https://github.com/sharetribe/flex-template-web/pull/892)
|
||||
* [add] Add support for user's current location as a default suggestion in the location autocomplete
|
||||
- [add] Add support for user's current location as a default suggestion in the location autocomplete
|
||||
search. [#895](https://github.com/sharetribe/flex-template-web/pull/895)
|
||||
* [add] Add support for default locations in the LocationAutocompleteInput component. Common
|
||||
- [add] Add support for default locations in the LocationAutocompleteInput component. Common
|
||||
searches can be configured to show when the input has focus. This reduces typing and Google Places
|
||||
geolocation API usage. The defaults can be configured in
|
||||
`src/components/LocationAutocompleteInput/GeocoderGoogleMaps.js`.
|
||||
[#894](https://github.com/sharetribe/flex-template-web/pull/894)
|
||||
* [change] Removed the `country` parameter from the search page as it was not used anywhere.
|
||||
- [change] Removed the `country` parameter from the search page as it was not used anywhere.
|
||||
[#893](https://github.com/sharetribe/flex-template-web/pull/893)
|
||||
|
||||
## v1.3.2 2018-08-07
|
||||
[v1.4.0]: https://github.com/sharetribe/flex-template-web/compare/v1.3.2...v1.4.0
|
||||
|
||||
* [change] Update the Sharetribe Flex SDK to the 1.0.0 version in NPM. All the `sharetribe-sdk`
|
||||
## [v1.3.2] 2018-08-07
|
||||
|
||||
- [change] Update the Sharetribe Flex SDK to the 1.0.0 version in NPM. All the `sharetribe-sdk`
|
||||
imports are now using the new package name `sharetribe-flex-sdk`.
|
||||
[#884](https://github.com/sharetribe/flex-template-web/pull/884)
|
||||
* [change] Reusable SearchMap. Fixed the original reverted version. (Includes audit exception 678)
|
||||
- [change] Reusable SearchMap. Fixed the original reverted version. (Includes audit exception 678)
|
||||
[#882](https://github.com/sharetribe/flex-template-web/pull/882)
|
||||
|
||||
## v1.3.1
|
||||
[v1.3.2]: https://github.com/sharetribe/flex-template-web/compare/v1.3.1...v1.3.2
|
||||
|
||||
* [fix] Hotfix: reverting the usage of ReusableMapContainer due to production build error.
|
||||
## [v1.3.1]
|
||||
|
||||
- [fix] Hotfix: reverting the usage of ReusableMapContainer due to production build error.
|
||||
[#881](https://github.com/sharetribe/flex-template-web/pull/881)
|
||||
|
||||
## v1.3.0
|
||||
[v1.3.1]: https://github.com/sharetribe/flex-template-web/compare/v1.3.0...v1.3.1
|
||||
|
||||
* [change] Reusable SearchMap. [#877](https://github.com/sharetribe/flex-template-web/pull/877)
|
||||
* [fix] Fix a search filters panel bug where selecting an option in a multi select filter ends up
|
||||
## [v1.3.0]
|
||||
|
||||
- [change] Reusable SearchMap. [#877](https://github.com/sharetribe/flex-template-web/pull/877)
|
||||
- [fix] Fix a search filters panel bug where selecting an option in a multi select filter ends up
|
||||
invoking a mobile filter callback function.
|
||||
[#876](https://github.com/sharetribe/flex-template-web/pull/876)
|
||||
* [change] Use seeded random for client side coordinate obfuscation
|
||||
- [change] Use seeded random for client side coordinate obfuscation
|
||||
[#874](https://github.com/sharetribe/flex-template-web/pull/874)
|
||||
|
||||
## v1.2.2
|
||||
[v1.3.0]: https://github.com/sharetribe/flex-template-web/compare/v1.2.2...v1.3.0
|
||||
|
||||
* [change] Change static map to dynamic map when clicked.
|
||||
## [v1.2.2]
|
||||
|
||||
- [change] Change static map to dynamic map when clicked.
|
||||
[#871](https://github.com/sharetribe/flex-template-web/pull/871)
|
||||
|
||||
## v1.2.1
|
||||
[v1.2.2]: https://github.com/sharetribe/flex-template-web/compare/v1.2.1...v1.2.2
|
||||
|
||||
* [fix] Lazy load map only if the map is near current viewport.
|
||||
## [v1.2.1]
|
||||
|
||||
- [fix] Lazy load map only if the map is near current viewport.
|
||||
[#871](https://github.com/sharetribe/flex-template-web/pull/871)
|
||||
|
||||
## v1.2.0
|
||||
[v1.2.1]: https://github.com/sharetribe/flex-template-web/compare/v1.2.0...v1.2.1
|
||||
|
||||
* [change] Use Google's static map on ListingPage. This is a reaction to pricing change of Google
|
||||
## [v1.2.0]
|
||||
|
||||
- [change] Use Google's static map on ListingPage. This is a reaction to pricing change of Google
|
||||
Maps APIs. [#869](https://github.com/sharetribe/flex-template-web/pull/869)
|
||||
* [change] Use sessionTokens and fields for Autocomplete calls to Google Maps. This is a reaction to
|
||||
- [change] Use sessionTokens and fields for Autocomplete calls to Google Maps. This is a reaction to
|
||||
pricing change of Google Maps APIs.
|
||||
[#867](https://github.com/sharetribe/flex-template-web/pull/867)
|
||||
* [change] Change TransactionPage state management in loadData.
|
||||
- [change] Change TransactionPage state management in loadData.
|
||||
[#863](https://github.com/sharetribe/flex-template-web/pull/863),
|
||||
[#865](https://github.com/sharetribe/flex-template-web/pull/865) &
|
||||
[#866](https://github.com/sharetribe/flex-template-web/pull/866)
|
||||
* [fix] Fix submit button state on contact details page.
|
||||
- [fix] Fix submit button state on contact details page.
|
||||
[#864](https://github.com/sharetribe/flex-template-web/pull/864)
|
||||
* [fix] Fix listing page host section layout bug.
|
||||
- [fix] Fix listing page host section layout bug.
|
||||
[#862](https://github.com/sharetribe/flex-template-web/pull/862)
|
||||
* [fix] Fix initial message input clearing too early in checkout page.
|
||||
- [fix] Fix initial message input clearing too early in checkout page.
|
||||
[#861](https://github.com/sharetribe/flex-template-web/pull/861)
|
||||
* [fix] Fix setting Topbar search input initial value.
|
||||
* [change] Update Redux to v4 [#859](https://github.com/sharetribe/flex-template-web/pull/859)
|
||||
* [fix] Fix setting Topbar search input initial value
|
||||
- [fix] Fix setting Topbar search input initial value.
|
||||
- [change] Update Redux to v4 [#859](https://github.com/sharetribe/flex-template-web/pull/859)
|
||||
- [fix] Fix setting Topbar search input initial value
|
||||
[#857](https://github.com/sharetribe/flex-template-web/pull/857)
|
||||
|
||||
## v1.1.0
|
||||
[v1.2.0]: https://github.com/sharetribe/flex-template-web/compare/v1.1.0...v1.2.0
|
||||
|
||||
* [fix] Improve slug creation (slashes were breaking rendering in some environments)
|
||||
## [v1.1.0]
|
||||
|
||||
- [fix] Improve slug creation (slashes were breaking rendering in some environments)
|
||||
[#850](https://github.com/sharetribe/flex-template-web/pull/850)
|
||||
* [fix] Anonymous user should see contact link on UserCard
|
||||
- [fix] Anonymous user should see contact link on UserCard
|
||||
[#851](https://github.com/sharetribe/flex-template-web/pull/851)
|
||||
* [fix] Persisting booking request details across authentication
|
||||
- [fix] Persisting booking request details across authentication
|
||||
[#852](https://github.com/sharetribe/flex-template-web/pull/852)
|
||||
* [change] Footer styles changed to more generic (no disappearing columns etc.) If you have made
|
||||
- [change] Footer styles changed to more generic (no disappearing columns etc.) If you have made
|
||||
changes to Footer, consider extracting it to different component before update.
|
||||
[#853](https://github.com/sharetribe/flex-template-web/pull/853)
|
||||
* [change] Logo customization refactored to be easier. Check CheckoutPage, TopbarDesktop and Footer
|
||||
- [change] Logo customization refactored to be easier. Check CheckoutPage, TopbarDesktop and Footer
|
||||
after update. [#854](https://github.com/sharetribe/flex-template-web/pull/854)
|
||||
* [fix] Fix showing reviews from banned users.
|
||||
- [fix] Fix showing reviews from banned users.
|
||||
[#855](https://github.com/sharetribe/flex-template-web/pull/855)
|
||||
|
||||
## v1.0.0
|
||||
[v1.1.0]: https://github.com/sharetribe/flex-template-web/compare/v1.0.0...v1.1.0
|
||||
|
||||
* [change] Migrate remaining Redux Forms to Final Form. Also now all the form components can be
|
||||
## [v1.0.0]
|
||||
|
||||
- [change] Migrate remaining Redux Forms to Final Form. Also now all the form components can be
|
||||
found in the src/forms folder. Remove redux-form from the dependencies.
|
||||
[#845](https://github.com/sharetribe/flex-template-web/pull/845)
|
||||
* [fix] Extract and fix missing information reminder modal from Topbar
|
||||
- [fix] Extract and fix missing information reminder modal from Topbar
|
||||
[#846](https://github.com/sharetribe/flex-template-web/pull/846)
|
||||
* [fix] Add missing styles for ModalMissingInformation from Topbar
|
||||
- [fix] Add missing styles for ModalMissingInformation from Topbar
|
||||
[#847](https://github.com/sharetribe/flex-template-web/pull/847)
|
||||
* [fix] API does not return all image variants anymore, this adds correct variants to update contact
|
||||
- [fix] API does not return all image variants anymore, this adds correct variants to update contact
|
||||
details call. [#848](https://github.com/sharetribe/flex-template-web/pull/848)
|
||||
|
||||
## v0.3.1
|
||||
[v1.0.0]: https://github.com/sharetribe/flex-template-web/compare/v0.3.1...v1.0.0
|
||||
|
||||
* [change] Change lodash import syntax to reduce bundle size (-15.14 KB)
|
||||
## [v0.3.1]
|
||||
|
||||
- [change] Change lodash import syntax to reduce bundle size (-15.14 KB)
|
||||
[#839](https://github.com/sharetribe/flex-template-web/pull/839)
|
||||
* [fix] Use https instead of git to access SDK repo for Heroku build (now that the repo is public).
|
||||
- [fix] Use https instead of git to access SDK repo for Heroku build (now that the repo is public).
|
||||
TODO: create SDK releases instead of using direct refs to single commit.
|
||||
[#841](https://github.com/sharetribe/flex-template-web/pull/841)
|
||||
* [fix] Typo fix for background-color
|
||||
- [fix] Typo fix for background-color
|
||||
[#842](https://github.com/sharetribe/flex-template-web/pull/842)
|
||||
|
||||
## v0.3.0
|
||||
[v0.3.1]: https://github.com/sharetribe/flex-template-web/compare/v0.3.0...v0.3.1
|
||||
|
||||
* Remove custom touched handling from `FieldCheckboxGroup` as it has has become obsolete now that
|
||||
## [v0.3.0]
|
||||
|
||||
- Remove custom touched handling from `FieldCheckboxGroup` as it has has become obsolete now that
|
||||
Final Form is replacing Redux Form.
|
||||
[#837](https://github.com/sharetribe/flex-template-web/pull/837)
|
||||
* Create Stripe account directly instead of passing payout details to Flex API (deprecated way).
|
||||
- Create Stripe account directly instead of passing payout details to Flex API (deprecated way).
|
||||
[#836](https://github.com/sharetribe/flex-template-web/pull/836)
|
||||
|
||||
[v0.3.0]: https://github.com/sharetribe/flex-template-web/compare/v0.2.0...v0.3.0
|
||||
|
||||
## v0.2.0
|
||||
|
||||
* Starting a change log for Flex Template for Web.
|
||||
- Starting a change log for Flex Template for Web.
|
||||
|
|
|
|||
38
README.md
|
|
@ -15,26 +15,50 @@ If you just want to get the app running quickly to test it out, first install
|
|||
```sh
|
||||
git clone git@github.com:sharetribe/flex-template-web.git # clone this repository
|
||||
cd flex-template-web/ # change to the cloned directory
|
||||
cp .env-template .env # copy the env template file to add your local config
|
||||
emacs .env # in your favorite editor, add the mandatory env vars to the config
|
||||
yarn install # install dependencies
|
||||
yarn run config # add the mandatory env vars to your local config
|
||||
yarn run dev # start the dev server, this will open a browser in localhost:3000
|
||||
```
|
||||
|
||||
See the [Environment configuration variables](docs/env.md) documentation for more information of the
|
||||
required configuration variables.
|
||||
You can also follow along the
|
||||
[Getting started with FTW](https://www.sharetribe.com/docs/tutorials/getting-started-with-ftw/)
|
||||
tutorial in the [Flex Docs website](https://www.sharetribe.com/docs/).
|
||||
|
||||
For more information of the configuration, see the
|
||||
[FTW Environment configuration variables](https://www.sharetribe.com/docs/references/ftw-env/)
|
||||
reference in Flex Docs.
|
||||
|
||||
**Note:** If you want to build your own Flex marketplace on top of the template, you should fork the
|
||||
repository instead of cloning it. See the [Customization guide](./docs/customization-guide.md).
|
||||
repository instead of cloning it. See the
|
||||
[How to Customize FTW](https://www.sharetribe.com/docs/guides/how-to-customize-ftw/) guide in Flex
|
||||
Docs.
|
||||
|
||||
## Getting started with your own customization
|
||||
|
||||
If you want to build your own Flex marketplace by customizing the template application, see the
|
||||
[Customization guide](docs/customization-guide.md) documentation.
|
||||
[How to Customize FTW](https://www.sharetribe.com/docs/guides/how-to-customize-ftw/) guide in Flex
|
||||
Docs.
|
||||
|
||||
## Deploying to Heroku
|
||||
|
||||
**Note:** Remember to fork the repository before deploying the application. Connecting your own
|
||||
Github repository to Heroku will make manual deploys easier.
|
||||
|
||||
See the
|
||||
[How to deploy FTW to production](https://www.sharetribe.com/docs/guides/how-to-deploy-ftw-to-production/)
|
||||
guide in Flex Docs for more information.
|
||||
|
||||
[](https://heroku.com/deploy)
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation can be found in the [docs directory](docs/).
|
||||
See the Flex Docs site: https://www.sharetribe.com/docs/
|
||||
|
||||
See also the [docs/](docs/) directory for some additional internal documentation.
|
||||
|
||||
## Get help – join Sharetribe Flex Developer Slack channel
|
||||
|
||||
If you have any questions about development, the best place to ask them is the Flex Developer Slack channel at https://www.sharetribe.com/flex-slack
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
47
app.json
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "Sharetribe Flex Template Demo",
|
||||
"description": "Sharetribe Flex Template Demo - Saunatime!",
|
||||
"keywords": [
|
||||
"node",
|
||||
"react"
|
||||
],
|
||||
"repository": "https://github.com/sharetribe/flex-template-web/",
|
||||
"env": {
|
||||
"REACT_APP_MAPBOX_ACCESS_TOKEN": {
|
||||
"description": "See: Integrating to map providers documentation https://github.com/sharetribe/flex-template-web/blob/master/docs/map-providers.md"
|
||||
},
|
||||
"REACT_APP_SHARETRIBE_SDK_CLIENT_ID": {
|
||||
"description": "Client ID (API key). You will get this from the Sharetribe team."
|
||||
},
|
||||
"REACT_APP_STRIPE_PUBLISHABLE_KEY": {
|
||||
"description": "Stripe publishable API key for generating tokens with Stripe API. Use test key (prefix pktest) for development."
|
||||
},
|
||||
"REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY": {
|
||||
"description": "The currency used in the Marketplace as ISO 4217 currency code. For example: USD, EUR, CAD, AUD, etc.",
|
||||
"value": "USD"
|
||||
},
|
||||
"REACT_APP_CANONICAL_ROOT_URL": {
|
||||
"description": "Canonical root url of the marketplace. E.g. https://the-name-of-your-app.herokuapp.com or your actual domain. This is needed for social media sharing and SEO optimization."
|
||||
},
|
||||
"NODE_ENV": {
|
||||
"description": "Node env. Use 'development' for development and 'production' for production.",
|
||||
"value": "production"
|
||||
},
|
||||
"REACT_APP_ENV": {
|
||||
"description": "A more fine grained env definition than NODE_ENV. Is used for example to differentiate envs in logging.",
|
||||
"value": "production"
|
||||
},
|
||||
"REACT_APP_SHARETRIBE_USING_SSL": {
|
||||
"description": "Redirect HTTP to HTTPS?",
|
||||
"value": "true"
|
||||
},
|
||||
"SERVER_SHARETRIBE_TRUST_PROXY": {
|
||||
"description": "Set when running the app behind a reverse proxy, e.g. in Heroku.",
|
||||
"value": "true"
|
||||
},
|
||||
"REACT_APP_CSP": {
|
||||
"description": "See: Content Security Policy (CSP) documentation https://github.com/sharetribe/flex-template-web/blob/master/docs/content-security-policy.md",
|
||||
"value": "report"
|
||||
}
|
||||
}
|
||||
}
|
||||
180
docs/README.md
|
|
@ -13,45 +13,159 @@ used in a browser environment using the JavaScript SDK.
|
|||
|
||||
Marketplace features included in Saunatime:
|
||||
|
||||
* **Location search** shows the user if there are saunas available in a given location.
|
||||
* **The listing page** gives a detailed view about what a particular sauna offers.
|
||||
* **Transaction process**: Saunatime uses nightly booking.
|
||||
* **Notifications**: Emails are sent during the booking process to alert customers and providers
|
||||
- **Location search** shows the user if there are saunas available in a given location.
|
||||
- **The listing page** gives a detailed view about what a particular sauna offers.
|
||||
- **Transaction process**: Saunatime uses nightly booking.
|
||||
- **Notifications**: Emails are sent during the booking process to alert customers and providers
|
||||
about changes in the booking state.
|
||||
* **Inbox** lists **orders** and **sales**.
|
||||
* **Reviews** can be given after a completed transaction.
|
||||
* **User profiles** provide detailed information about a given user.
|
||||
* **Extended data:** The listing and user data models are modified using extended data.
|
||||
- **Inbox** lists **orders** and **sales**.
|
||||
- **Reviews** can be given after a completed transaction.
|
||||
- **User profiles** provide detailed information about a given user.
|
||||
- **Extended data:** The listing and user data models are modified using extended data.
|
||||
|
||||
## Directory structure
|
||||
|
||||
After cloning the repo, your project should look like this:
|
||||
|
||||
```
|
||||
├── ext/
|
||||
│ └── default-mail-templates/
|
||||
├── package.json
|
||||
├── docs/
|
||||
├── public/
|
||||
│ ├── static/
|
||||
│ ├── 500.html
|
||||
│ ├── index.html
|
||||
│ └── robots.txt
|
||||
├── server/
|
||||
│ ├── csp.js
|
||||
│ ├── index.js
|
||||
│ ├── sitemap.js
|
||||
│ └ ...
|
||||
└── src/
|
||||
├── analytics/
|
||||
├── assets/
|
||||
├── components/
|
||||
├── containers/
|
||||
├── ducks/
|
||||
├── forms/
|
||||
├── translations/
|
||||
├── util/
|
||||
├── Routes.js
|
||||
├── app.js
|
||||
├── config.js
|
||||
├── examples.js
|
||||
├── index.js
|
||||
├── marketplace-custom-config.js
|
||||
├── marketplace.css
|
||||
├── marketplaceFonts.css
|
||||
├── marketplaceIndex.css
|
||||
├── reducers.js
|
||||
├── routeConfiguration.js
|
||||
└── store.js
|
||||
```
|
||||
|
||||
### public/index.html
|
||||
|
||||
This is the page template. It includes fonts, Stripe SDK, Mapbox API (and Google Maps JavaScript
|
||||
API), and app icons. It also specifies placeholders for tags generated by
|
||||
[React Helmet](https://github.com/nfl/react-helmet)
|
||||
|
||||
### src/
|
||||
|
||||
- `src/index.js` is the JavaScript entry point on the client-side (it also exports `renderApp`
|
||||
function to be used for server-side rendering).
|
||||
- `src/app.js` creates ClientApp and ServerApp
|
||||
- `src/routeConfiguration.js` defines logic between routes and page-level components
|
||||
- `src/marketplace.css`, `src/marketplaceIndex.css`, `src/marketplaceIndex.css` define marketplace
|
||||
level styling using [CSS Properties](http://cssnext.io/features/#custom-properties-var) and CSS
|
||||
Property Sets.
|
||||
- `src/translations/en.json` all English translations used in Saunatime template app. (A good place
|
||||
to start customization.)
|
||||
|
||||
#### `src/components/`
|
||||
|
||||
Normal components used in Flex Template for Web are defined here. The main file for the component is
|
||||
behind path `src/components/ComponentName/ComponentName.js`, but you can import it through
|
||||
`component/index.js`:
|
||||
|
||||
`import { NamedLink } from 'path/to/components';`
|
||||
|
||||
When creating new components, remember to import marketplace.css file to component's own CSS file -
|
||||
that way you can use CSS Property variables defined in `src/marketplace.css`.
|
||||
|
||||
#### `src/containers/`
|
||||
|
||||
Containers are components that are connected to [Redux](https://redux.js.org/) store. Currently,
|
||||
this folder contains only page-level components and one common component: TopbarContainer.
|
||||
|
||||
#### `src/forms/`
|
||||
|
||||
Forms are in their own folder since they are using a special library to handle validations, errors,
|
||||
internal state, etc.
|
||||
|
||||
### server/
|
||||
|
||||
- `server/index.js` handles server-side rendering (SSR). Built with [Express](http://expressjs.com)
|
||||
(Node.js framework).
|
||||
- `server/csp.js` [Content Security Policy](https://content-security-policy.com), whitelist of
|
||||
domains that the application and loaded scripts are allowed to access from the browser.
|
||||
- `server/sitemap.js` generates a minimal sitemap.xml for SEO purposes.
|
||||
|
||||
### ext/default-mail-templates/
|
||||
|
||||
This directory contains default email templates for transaction-specific needs (e.g.
|
||||
_booking-request-accepted_). However, there are some built-in email templates that you can change in
|
||||
[Flex Console](http://flex-console.sharetribe.com/email-templates/email-changed).
|
||||
|
||||
_**NOTE:** Changing these doesn't change actual email templates - you need to send customized
|
||||
templates to your Flex contact person._
|
||||
|
||||
## Getting started
|
||||
|
||||
We recommend going through the
|
||||
[Getting started articles](https://www.sharetribe.com/docs/background/getting-started/) in the Flex
|
||||
Docs:
|
||||
|
||||
- [Introducing Flex](https://www.sharetribe.com/docs/background/introducing-flex/)
|
||||
- [What development skills are needed?](https://www.sharetribe.com/docs/background/development-skills/)
|
||||
- [Getting started with FTW](https://www.sharetribe.com/docs/tutorials/getting-started-with-ftw/)
|
||||
|
||||
## Customization guide
|
||||
|
||||
The easiest way to start a customization project is to read through the
|
||||
[How to Customize FTW](https://www.sharetribe.com/docs/guides/how-to-customize-ftw/) guide in Flex
|
||||
Docs.
|
||||
|
||||
## How to customize this template
|
||||
|
||||
The easiest way to start a customization project is to read through the
|
||||
[customization guide](customization-guide.md). After that, you probably should start with changing
|
||||
[translations](translations.md), [common styles](styling.md) in marketplace.css, and then create
|
||||
[static pages](static-pages.md) and modify
|
||||
[Terms of Service and Privacy Policy pages](terms-of-service-and-privacy-policy.md).
|
||||
After going through the customization guide, you should probably start with the following guides in
|
||||
Flex Docs:
|
||||
|
||||
Documentation for specific topics can be found in the following files:
|
||||
- [How to set up Mapbox for FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-mapbox-for-ftw/)
|
||||
- [How to change FTW UI texts and translations](https://www.sharetribe.com/docs/guides/how-to-change-ftw-ui-texts-and-translations/)
|
||||
- [How to customize FTW styles](https://www.sharetribe.com/docs/guides/how-to-customize-ftw-styles/)
|
||||
- [How to change Terms of Service and Privacy Policy in FTW](https://www.sharetribe.com/docs/guides/how-to-change-tos-and-privacy-policy-in-ftw/)
|
||||
- [How to change FTW icons](https://www.sharetribe.com/docs/guides/how-to-change-ftw-icons/)
|
||||
- [FTW customization checklist](https://www.sharetribe.com/docs/guides/ftw-customization-checklist/)
|
||||
- [How to deploy FTW to production](https://www.sharetribe.com/docs/guides/how-to-deploy-ftw-to-production/)
|
||||
- [How routing works in FTW](https://www.sharetribe.com/docs/background/ftw-routing/)
|
||||
- [How the Redux setup works in FTW](https://www.sharetribe.com/docs/background/ftw-redux/)
|
||||
|
||||
* [Customization guide](customization-guide.md)
|
||||
* [Folder structure](folder-structure.md)
|
||||
* [Integration to map providers](map-providers.md)
|
||||
* [Translations](translations.md)
|
||||
* [Styling a marketplace](styling.md)
|
||||
* [Static pages](static-pages.md)
|
||||
* [Terms of Service and Privacy Policy](terms-of-service-and-privacy-policy.md)
|
||||
* [Extended data](extended-data.md)
|
||||
* [Extend the listing data model](extend-listing.md)
|
||||
* [Search filters](search-filters.md)
|
||||
* [Testing](testing.md)
|
||||
* [Error logging with Sentry](sentry.md)
|
||||
* [Analytics](analytics.md)
|
||||
* [Continuous Integration (CI)](ci.md)
|
||||
* [Content Security Policy (CSP)](content-security-policy.md)
|
||||
* [Original create-react-app documentation](https://github.com/sharetribe/create-react-app/blob/master/packages/react-scripts/template/README.md)
|
||||
* [Customization checklist](customization-checklist.md)
|
||||
* [Icons](icons.md)
|
||||
* [Improving performance](improving-performance.md)
|
||||
See also the following articles:
|
||||
|
||||
- [How to extend listing data in FTW](https://www.sharetribe.com/docs/guides/how-to-extend-listing-data-in-ftw/)
|
||||
- [How to change search filters in FTW](https://www.sharetribe.com/docs/guides/how-to-change-search-filters-in-ftw/)
|
||||
- [How to add static pages in FTW](https://www.sharetribe.com/docs/guides/how-to-add-static-pages-in-ftw/)
|
||||
- [How to test FTW](https://www.sharetribe.com/docs/guides/how-to-test-ftw/)
|
||||
- [How to set up Sentry to log errors in FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-sentry-to-log-errors-in-ftw/)
|
||||
- [How to set up Analytics for FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-analytics-for-ftw/)
|
||||
- [How to set up Content Security Policy (CSP) for FTW](https://www.sharetribe.com/docs/guides/how-to-set-up-csp-for-ftw/)
|
||||
- [How to use CI with FTW](https://www.sharetribe.com/docs/guides/how-to-use-ci-with-ftw/)
|
||||
- [How to improve performance](https://www.sharetribe.com/docs/guides/how-to-improve-performance/)
|
||||
|
||||
See also the
|
||||
[original create-react-app documentation](https://github.com/sharetribe/create-react-app/blob/master/packages/react-scripts/template/README.md).
|
||||
|
||||
The application was bootstrapped with a forked version of
|
||||
[create-react-app](https://github.com/facebookincubator/create-react-app). While most of the
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
theme: jekyll-theme-minimal
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ site.lang | default: "en-US" }}">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{% seo %}
|
||||
<link rel="stylesheet" href="{{ "/assets/css/style.css?v=" | append: site.github.build_revision | relative_url }}">
|
||||
<!--[if lt IE 9]>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
|
||||
<![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<h1><a href="{{ "/" | absolute_url }}">{{ site.title | default: site.github.repository_name }}</a></h1>
|
||||
|
||||
{% if site.logo %}
|
||||
<img src="{{site.logo | relative_url}}" alt="Logo" />
|
||||
{% endif %}
|
||||
|
||||
<p>{{ site.description | default: site.github.project_tagline }}</p>
|
||||
|
||||
{% if site.github.is_project_page %}
|
||||
<p class="view"><a href="{{ site.github.repository_url }}">View the Project on GitHub <small>{{ site.github.repository_nwo }}</small></a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if site.github.is_user_page %}
|
||||
<p class="view"><a href="{{ site.github.owner_url }}">View My GitHub Profile</a></p>
|
||||
{% endif %}
|
||||
|
||||
{% if site.show_downloads %}
|
||||
<ul>
|
||||
<li><a href="{{ site.github.zip_url }}">Download <strong>ZIP File</strong></a></li>
|
||||
<li><a href="{{ site.github.tar_url }}">Download <strong>TAR Ball</strong></a></li>
|
||||
<li><a href="{{ site.github.repository_url }}">View On <strong>GitHub</strong></a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
</header>
|
||||
<aside>Navigation</aside>
|
||||
<section>
|
||||
|
||||
{{ content }}
|
||||
|
||||
</section>
|
||||
<footer>
|
||||
{% if site.github.is_project_page %}
|
||||
<p>This project is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a></p>
|
||||
{% endif %}
|
||||
<p><small>Hosted on GitHub Pages — Theme by <a href="https://github.com/orderedlist">orderedlist</a></small></p>
|
||||
</footer>
|
||||
</div>
|
||||
<script src="{{ "/assets/js/scale.fix.js" | relative_url }}"></script>
|
||||
{% if site.google_analytics %}
|
||||
<script>
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '{{ site.google_analytics }}', 'auto');
|
||||
ga('send', 'pageview');
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,37 +1,5 @@
|
|||
# Analytics
|
||||
|
||||
The application supports tracking page views with pluggable analytics handlers.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Google Analytics
|
||||
|
||||
Google Analytics (GA) is supported by default. The enable GA, set the GA tracker ID to the
|
||||
environment variable `REACT_APP_GOOGLE_ANALYTICS_ID`;
|
||||
|
||||
## Custom analytics handler
|
||||
|
||||
If you want to add a new analytics library, you can do as follows:
|
||||
|
||||
1. Add the analytics library script
|
||||
|
||||
If the analytics library has an external script, add the library script tag to the
|
||||
[src/public/index.html](../public/index.html) file. If you need more control, see how the GA
|
||||
script is added in [server/renderer.js](../server/renderer.js).
|
||||
|
||||
1. Create a handler
|
||||
|
||||
To track page views, create a custom handler e.g. in
|
||||
[src/analytics/handlers.js](../src/analytics/handlers.js). The handler should be a class that
|
||||
implements a `trackPageView(url)` method.
|
||||
|
||||
Note that the `url` parameter might not be the same as in the URL bar of the browser. It is the
|
||||
canonical form of the URL. For example, in the listing page it doesn't have the dynamic title
|
||||
slug in the middle. This allows unified analytics and correct tracking of pages that can be
|
||||
accessed from multiple different URLs.
|
||||
|
||||
If you analytics library takes the page URL from the browser, you might need to override that
|
||||
behavior to use the canonical URL that is given to the method.
|
||||
|
||||
1. Initialise the handler
|
||||
|
||||
Initialise the handler in the `setupAnalyticsHandlers()` function in
|
||||
[src/index.js](../src/index.js).
|
||||
https://www.sharetribe.com/docs/guides/how-to-set-up-analytics-for-ftw/
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 221 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 373 KiB |
33
docs/ci.md
|
|
@ -1,34 +1,5 @@
|
|||
# CI
|
||||
|
||||
The application provides a configuration to use [CircleCI](https://circleci.com/) as a continuous
|
||||
integration server to run tests and other scripts. This ensures quality and can be forced to avoid
|
||||
merging changes that break tests or fail audits.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
The [circle.yml](../circle.yml) file is used to configure CircleCI.
|
||||
|
||||
Currently the CI runs the following scripts:
|
||||
|
||||
## Security audit: `yarn run audit`
|
||||
|
||||
This command runs the security audit using [nsp](https://www.npmjs.com/package/nsp). The audit
|
||||
checks for installed packages with known vulnerabilities and warns about those.
|
||||
|
||||
The scripts outputs information about the dependency path that added the package. If that
|
||||
information is not enough, `yarn why package-name` can be used to get more detailed information
|
||||
about why the package is installed.
|
||||
|
||||
If you are convinced that the vulnerability can be ignored, exceptions can be added to the
|
||||
[.nsprc](../.nsprc) file.
|
||||
|
||||
## Code formatting: `yarn run format-ci`
|
||||
|
||||
This command fails if there are changes in the formatting that are not committed. Run
|
||||
`yarn run format` to format the code and get rid of the error.
|
||||
|
||||
## Build: `yarn run build`
|
||||
|
||||
This command ensures that the build passes.
|
||||
|
||||
## Tests: `yarn run test-ci`
|
||||
|
||||
This command runs the tests.
|
||||
https://www.sharetribe.com/docs/guides/how-to-use-ci-with-ftw/
|
||||
|
|
|
|||
|
|
@ -1,43 +1,5 @@
|
|||
# Content Security Policy (CSP)
|
||||
|
||||
According to [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP):
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
> Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate
|
||||
> certain types of attacks, including Cross Site Scripting (XSS) and data injection attacks. These
|
||||
> attacks are used for everything from data theft to site defacement or distribution of malware.
|
||||
|
||||
This document describes how to use a CSP with the Flex template app. By default the CSP is disabled.
|
||||
By turning it on, the default whitelist in [server/csp.js](../server/csp.js) works with the all the
|
||||
URLs and tools that come with the application.
|
||||
|
||||
## Setup
|
||||
|
||||
The CSP is configured using the `REACT_APP_CSP` environment variable.
|
||||
|
||||
Possible values:
|
||||
|
||||
* not set: disabled
|
||||
* `REACT_APP_CSP=report`: Enabled, but policy violations are only reported
|
||||
* `REACT_APP_CSP=block`: Enabled. Policy violations are reported and requests that violate the
|
||||
policy are blocked
|
||||
|
||||
If error logging with Sentry is enabled (See [Error logging with Sentry](sentry.md)), the reports
|
||||
are sent to Sentry. Otherwise the reports are just logged in the backend.
|
||||
|
||||
## Extending the policy
|
||||
|
||||
If you want to whitelist new sources (for example adding a new external analytics service) and keep
|
||||
the CSP enabled, you should add the domains to the white list in [server/csp.js](../server/csp.js).
|
||||
|
||||
The easiest way to do this is to first turn on the policy in report mode and then see what policy
|
||||
violations are logged to the browser developer console or to the backend policy violation report
|
||||
URL.
|
||||
|
||||
## Resources
|
||||
|
||||
To understand what CSP is and how browsers work, here are some resources:
|
||||
|
||||
* https://content-security-policy.com/
|
||||
* https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
|
||||
* https://ponyfoo.com/articles/content-security-policy-in-express-apps
|
||||
* https://helmetjs.github.io/docs/csp/
|
||||
https://www.sharetribe.com/docs/guides/how-to-set-up-csp-for-ftw/
|
||||
|
|
|
|||
|
|
@ -1,41 +1,5 @@
|
|||
# Customization checklist
|
||||
|
||||
Some generic things to update and check when starting to customize the template.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
* [Marketplace colors](styling.md)
|
||||
* [Generate app icons](icons.md)
|
||||
* [Update translations](../src/translations/en.json) or [change the language](translations.md)
|
||||
* [LandingPage](../src/containers/LandingPage/LandingPage.js): update and create branded sections
|
||||
* [Footer](../src/components/Footer/Footer.js)
|
||||
* [AboutPage](../src/containers/AboutPage/AboutPage.js)
|
||||
* [Terms of Service](terms-of-service-and-privacy-policy.md#terms-of-service)
|
||||
* [Privacy Policy](terms-of-service-and-privacy-policy.md#privacy-policy)
|
||||
* [Social media sharing graphics](../src/components/Page/Page.js);
|
||||
* [Logo](../src/components/Logo/Logo.js) Change and check that it works on Topbar, Footer, and
|
||||
CheckoutPage
|
||||
* [Default background image](../src/assets/background-1440.jpg)
|
||||
* [Maps Marker icon](../src/components/Map/images/marker-32x32.png)
|
||||
* [Config: update environment variables](../src/config.js)
|
||||
* [Config: siteTitle](../src/config.js) for page schema (SEO)
|
||||
* [Config: marketplace address](../src/config.js): contact details also improve SEO
|
||||
* [Config: social media pages](../src/config.js)
|
||||
* [Marketplace custom config](../src/marketplace-custom-config.js)
|
||||
* Update [ListingPage](../src/containers/ListingPage/ListingPage.js) to show extended data (aka
|
||||
publicData attribute)
|
||||
* Update [EditListingWizard](../src/components/EditListingWizard/EditListingWizard.js) and panels to
|
||||
add extended data
|
||||
* Update [SearchPage](../src/containers/SearchPage/SearchPage.js) to filter with extended data
|
||||
* Update [routeConfiguration](../src/routeConfiguration.js) if needed
|
||||
* Update [Email templates](../ext/default-mail-templates), all of them: .html, subject.txt, and
|
||||
text.txt
|
||||
* Update [config: bookingUnitType](../src/config.js) if needed
|
||||
* If `line-item/units` is used, add quantity handling to
|
||||
[BookingDatesForm](../src/forms/BookingDatesForm/BookingDatesForm.js),
|
||||
[ListingPage](../src/containers/ListingPage/ListingPage.js),
|
||||
[CheckoutPage](../src/containers/CheckoutPage/CheckoutPage.js)
|
||||
|
||||
## What else
|
||||
|
||||
* Do you need more [static pages](static-pages.md)?
|
||||
* Changes to existing pages
|
||||
* Changes to transaction process (API + Web app)
|
||||
https://www.sharetribe.com/docs/guides/ftw-customization-checklist/
|
||||
|
|
|
|||
|
|
@ -1,145 +1,5 @@
|
|||
# Customization guide
|
||||
|
||||
So you've decided to build your own Flex marketplace using the template. That's awesome! This guide
|
||||
will help you in setting up your fork and describes the general workflow.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
**Note:** If you cloned the repository like described in the Quick start section of the project
|
||||
README file, you probably don't want to make the customizations in that project. Forking the
|
||||
repository is the recommended way to proceed. Follow this guide for instructions.
|
||||
|
||||
## Requirements
|
||||
|
||||
Install required tools:
|
||||
|
||||
* [Node.js](https://nodejs.org/)
|
||||
* [Yarn](https://yarnpkg.com/)
|
||||
|
||||
## Technologies
|
||||
|
||||
Depending on what you want to change in the template, various skills help in achieving your goal.
|
||||
Some of the basic customizations don't require specific coding skills, but many customisations
|
||||
become technically involved. We've tried to keep the technology setup as simple as possible, and
|
||||
frontend developers with experience in widely used tooling should feel comfortable right from the
|
||||
get-go.
|
||||
|
||||
Here are some main technologies that the template uses:
|
||||
|
||||
* JavaScript: programming language for the whole application
|
||||
* [React](https://reactjs.org/): library for creating user interfaces with components
|
||||
* CSS: styling the user interface using [CSS Modules](https://github.com/css-modules/css-modules)
|
||||
and [cssnext](http://cssnext.io/)
|
||||
* [Redux](https://redux.js.org/): state and data flow handling
|
||||
* [Final Form](https://github.com/final-form/final-form): forms
|
||||
* [React Router](https://reacttraining.com/react-router/): routing
|
||||
* [Express](https://expressjs.com/): server
|
||||
|
||||
## Setup
|
||||
|
||||
To start a new customization project, you should create a separate Git repository and setup the Git
|
||||
remotes so that you can pull in changes from the main (upstream) repository to your custom
|
||||
repository.
|
||||
|
||||
### Fork the repository
|
||||
|
||||
See the [Fork a repo](https://help.github.com/articles/fork-a-repo/) documentation for instructions
|
||||
for forking a repository in GitHub.
|
||||
|
||||
In the directory you want to create the project in:
|
||||
|
||||
git clone git@github.com:YOUR_USERNAME/YOUR_FORK.git # clone your fork
|
||||
cd YOUR_FORK # changed to the cloned directory
|
||||
git remote add upstream git@github.com:sharetribe/flex-template-web.git # add the template as the upstream remote
|
||||
|
||||
See also the
|
||||
[Configuring a remote for a fork](https://help.github.com/articles/configuring-a-remote-for-a-fork/)
|
||||
documentation.
|
||||
|
||||
### Pull in latest upstream changes
|
||||
|
||||
If you want to update your local customization project with changes in the template, you should pull
|
||||
in changes from the upstream remote.
|
||||
|
||||
**Note:** Depending on the changes you've made to the template, this might be hard/impossible
|
||||
depending on what has changed in the template. You should mainly think of the template being the
|
||||
starting point of your customization, not something that is constantly updated.
|
||||
|
||||
In the `master` branch (or in the branch you want to merge in the upstream changes):
|
||||
|
||||
1. Fetch the latest changes from the upstream repository:
|
||||
|
||||
```sh
|
||||
git fetch upstream
|
||||
```
|
||||
|
||||
1. Merge the changes to your local branch
|
||||
|
||||
```sh
|
||||
git merge upstream/master
|
||||
```
|
||||
|
||||
1. Fix possible merge conflicts, commit, and push/deploy.
|
||||
|
||||
See also the [Syncing a fork](https://help.github.com/articles/syncing-a-fork/) documentation.
|
||||
|
||||
## Configuration
|
||||
|
||||
There are some mandatory configuration, and some configuration that you most likely want to at least
|
||||
go through.
|
||||
|
||||
To get started, first copy the config template:
|
||||
|
||||
cp .env-template .env
|
||||
|
||||
The `.env` file is the place to add your local configuration. It is ignored in Git, so you'll have
|
||||
to add the corresponding configuration also to your server environment.
|
||||
|
||||
There are some mandatory configuration variables that are defined in the template. See the
|
||||
[Environment configuration variables](env.md) documentation for more information.
|
||||
|
||||
See also the [src/config.js](../src/config.js) file for more configuration options.
|
||||
|
||||
## Development
|
||||
|
||||
In your project root, install dependencies:
|
||||
|
||||
yarn install
|
||||
|
||||
To develop the application and to see changes live, start the frontend development server:
|
||||
|
||||
yarn run dev
|
||||
|
||||
**Known issues:**
|
||||
|
||||
* Adding/changing `import`s may not be synced properly with ESLint. You may see an error
|
||||
`Unable to resolve path to module` even though the module existing in right path. Restarting the
|
||||
server doesn't help. To solve the issue, you need to make a change to the file where the error
|
||||
occurs.
|
||||
|
||||
## Development Server
|
||||
|
||||
The usual way to develop the application is to use the frontend development server (see above).
|
||||
However, in production you likely want to use the server rendering setup. To develop the server
|
||||
rendering setup locally, run:
|
||||
|
||||
yarn run dev-server
|
||||
|
||||
This runs the frontend production build and starts the Express.js server in `server/index.js` that
|
||||
renders the application routes in the server. The server is automatically restarted when there are
|
||||
changes in the `server/` directory.
|
||||
|
||||
**Note:** this server does **not** pick up changes in the frontend application code. For that you
|
||||
need to build the client bundle by restarting the server manually.
|
||||
|
||||
## Tests
|
||||
|
||||
To start the test watcher, run
|
||||
|
||||
yarn test
|
||||
|
||||
See more in the [testing documentation](testing.md).
|
||||
|
||||
## Customization
|
||||
|
||||
There are many things that you should change in the default template, and many more that you can
|
||||
change. See the [Customization checklist](customization-checklist.md) documentation for more
|
||||
information.
|
||||
https://www.sharetribe.com/docs/guides/how-to-customize-ftw/
|
||||
|
|
|
|||
5
docs/deploying-to-production.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Deploying to production
|
||||
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
https://www.sharetribe.com/docs/guides/how-to-deploy-ftw-to-production/
|
||||
41
docs/env.md
|
|
@ -1,42 +1,5 @@
|
|||
# Environment configuration variables
|
||||
|
||||
The following configuration variables can be set to control the Flex template app behaviour. Most of
|
||||
them have defaults that work for development environment. For production deploys most should be set.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
| Variable | Description |
|
||||
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| REACT_APP_MAPBOX_ACCESS_TOKEN | See: [Integrating to map providers](./map-providers.md) |
|
||||
| REACT_APP_GOOGLE_MAPS_API_KEY | See: [Google Maps API key](./google-maps.md) (Alternative map provider) |
|
||||
| REACT_APP_SHARETRIBE_SDK_CLIENT_ID | Client ID (API key). You will get this from the Sharetribe team. |
|
||||
| REACT_APP_STRIPE_PUBLISHABLE_KEY | Stripe publishable API key for generating tokens with Stripe API. Use test key (prefix pk*test*) for development. |
|
||||
| REACT_APP_SHARETRIBE_SDK_BASE_URL | The base url to access the Sharetribe Flex Marketplace API. |
|
||||
| REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY | The currency used in the Marketplace as ISO 4217 currency code. For example: USD, EUR, CAD, AUD, etc. |
|
||||
| REACT_APP_CANONICAL_ROOT_URL | Canonical root url of the marketplace. Needed for social media sharing and SEO optimization. |
|
||||
| NODE_ENV | Node env. Use 'development' for development and 'production' for production. |
|
||||
| PORT | Port for server to accept connections. |
|
||||
| REACT_APP_ENV | A more fine grained env definition than NODE_ENV. Is used for example to differentiate envs in logging. |
|
||||
| REACT_APP_SHARETRIBE_USING_SSL | Redirect HTTP to HTTPS? |
|
||||
| SERVER_SHARETRIBE_TRUST_PROXY | Set when running the app behind a reverse proxy, e.g. in Heroku. |
|
||||
| REACT_APP_PUBLIC_SENTRY_DSN | See: [Error logging with Sentry](./sentry.md) |
|
||||
| SERVER_SENTRY_DSN | See: [Error logging with Sentry](./sentry.md) |
|
||||
| REACT_APP_CSP | See: [Content Security Policy (CSP)](./content-security-policy.md) |
|
||||
| BASIC_AUTH_USERNAME | Set to enable HTTP Basic Auth |
|
||||
| BASIC_AUTH_PASSWORD | Set to enable HTTP Basic Auth |
|
||||
| REACT_APP_GOOGLE_ANALYTICS_ID | See: [Google Analytics](./analytics.md) |
|
||||
| REACT_APP_AVAILABILITY_ENABLED | Enables availability calendar for listings. |
|
||||
| REACT_APP_DEFAULT_SEARCHES_ENABLED | Enables default search suggestions in location autocomplete search input. |
|
||||
|
||||
## Defining configuration
|
||||
|
||||
When the app is started locally with `yarn run dev` or `yarn run dev-server`, you can set
|
||||
environment variables by using the (gitignored) `.env` file. The repository contains a template file
|
||||
`.env-template` with default configuration. Just copy that as `.env` and edit as necessary.
|
||||
|
||||
In production, it's recommended that you set the configuration via env variables and do not deploy
|
||||
an .env file. The client application will only be packaged with env variables that start with
|
||||
REACT_APP. This way server secrets don't end up in client bundles.
|
||||
|
||||
**With deploys note that the configuration options are bundled in the client package at build
|
||||
time.** The configuration of the build environment must match run environment for things to work
|
||||
consistently. To apply changes to configuration values client bundle must be rebuilt. Just
|
||||
restarting the server is not enough.
|
||||
https://www.sharetribe.com/docs/references/ftw-env/
|
||||
|
|
|
|||
|
|
@ -1,475 +1,5 @@
|
|||
# Extend listing data
|
||||
|
||||
This guide walks you through the different steps required to expand the listing data model in your
|
||||
marketplace. We'll have a look on how the data is added, how it can be presented and finally how it
|
||||
can be used to filter searches.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
Adding new attributes to the data model relies on a Flex concept called
|
||||
[extended data](./extended-data.md).
|
||||
|
||||
Three main areas in extending the listing data model are:
|
||||
|
||||
* [Declare the attribute and it's possible values](#declare-the-attribute-and-its-possible-values)
|
||||
* [Edit the listing wizard](#edit-the-listing-wizard)
|
||||
* [Show the attribute on listing page](#show-the-attribute-on-listing-page)
|
||||
* [Use the attribute as a search filter](#use-the-attribute-as-a-search-filter)
|
||||
|
||||
In this guide we will extend the listing data model by adding a _capacity_ attribute.
|
||||
|
||||
## Add a new attribute
|
||||
|
||||
### Declare the attribute and its possible values
|
||||
|
||||
So, you have come up with a great new extension to the listing data model, fantastic! Depending of
|
||||
the type of data the new attribute is going to be used for it might make sense to define and store
|
||||
the possible values for the attribute. In case the data will be free form text there might not be
|
||||
use for this but in our case we wan't to fix the possible values that can be used for the capacity.
|
||||
Also if the attribute will be used as a search filter it helps to keep track of the possible values
|
||||
somewhere.
|
||||
|
||||
A commonly used way of storing the possible values for a listing attribute is to declare them in the
|
||||
[marketplace-custom-config.js](../src/marketplace-custom-config.js) file. Place the values in an
|
||||
array and export the variable containing the array. The exports from that file are included in the
|
||||
`src/config.js` file and are available as properties in `config.custom`. Search filters and some
|
||||
components used to edit and present the data rely on a data model of an array of objects that
|
||||
contain `key` and `label` properties.
|
||||
|
||||
For the capacity attribute we'll define a set of values that describe the capacity in a few ranges
|
||||
of how many people can fit into a given sauna:
|
||||
|
||||
```js
|
||||
export const capacityOptions = [
|
||||
{ key: '1to3', label: '1 to 3' },
|
||||
{ key: '4to6', label: '4 to 6' },
|
||||
{ key: '7to9', label: '7 to 9' },
|
||||
{ key: '10plus', label: '10 plus' },
|
||||
];
|
||||
```
|
||||
|
||||
### Edit the listing wizard
|
||||
|
||||
Next step is to add means for modifying the attribute data in listings. This is achieved by adding
|
||||
proper inputs to the `EditListingWizard`. It could probably make sense to add the input to the
|
||||
_description_ tab or modify the _amenities_ tab to also include capacity but for the sake of clarity
|
||||
let's add a new tab to the wizard. The new tab will be placed between the _amenities_ and _policy_
|
||||
tabs.
|
||||
|
||||
First lets declare the tab in `EditListingWizardTab`:
|
||||
|
||||
```js
|
||||
export const DESCRIPTION = 'description';
|
||||
export const FEATURES = 'features';
|
||||
export const CAPACITY = 'capacity';
|
||||
export const POLICY = 'policy';
|
||||
export const LOCATION = 'location';
|
||||
export const PRICING = 'pricing';
|
||||
export const PHOTOS = 'photos';
|
||||
|
||||
// EditListingWizardTab component supports these tabs
|
||||
export const SUPPORTED_TABS = [DESCRIPTION, FEATURES, CAPACITY, POLICY, LOCATION, PRICING, PHOTOS];
|
||||
```
|
||||
|
||||
Now in `EditListingWizard` we can take that tab declaration into use. Import the tab name variable
|
||||
from `EditListingWizardTab` and add it to the `TABS` array.
|
||||
|
||||
```js
|
||||
export const TABS = [DESCRIPTION, FEATURES, CAPACITY, POLICY, LOCATION, PRICING, PHOTOS];
|
||||
```
|
||||
|
||||
Also remember to add a label for the tab in the `tabLabel` function:
|
||||
|
||||
```js
|
||||
const tabLabel = (intl, tab) => {
|
||||
let key = null;
|
||||
if (tab === DESCRIPTION) {
|
||||
key = 'EditListingWizard.tabLabelDescription';
|
||||
} else if (tab === FEATURES) {
|
||||
key = 'EditListingWizard.tabLabelFeatures';
|
||||
} else if (tab === CAPACITY) {
|
||||
key = 'EditListingWizard.tabLabelCapacity';
|
||||
} else if (tab === POLICY) {
|
||||
key = 'EditListingWizard.tabLabelPolicy';
|
||||
} else if (tab === LOCATION) {
|
||||
key = 'EditListingWizard.tabLabelLocation';
|
||||
} else if (tab === PRICING) {
|
||||
key = 'EditListingWizard.tabLabelPricing';
|
||||
} else if (tab === PHOTOS) {
|
||||
key = 'EditListingWizard.tabLabelPhotos';
|
||||
}
|
||||
|
||||
return intl.formatMessage({ id: key });
|
||||
};
|
||||
```
|
||||
|
||||
The `tabCompleted` function keeps track of which data the user has already provided in order to tell
|
||||
which tabs are completed. As we will be storing the capacity information in the listing's _public
|
||||
data_ property (see the [extended data](./extended-data.md) documentation for more info on different
|
||||
extended data types) we shall look into that property when resolving whether the capacity tab has
|
||||
alerady been completed or not:
|
||||
|
||||
```js
|
||||
/**
|
||||
* Check if a wizard tab is completed.
|
||||
*
|
||||
* @param tab wizard's tab
|
||||
* @param listing is contains some specific data if tab is completed
|
||||
*
|
||||
* @return true if tab / step is completed.
|
||||
*/
|
||||
const tabCompleted = (tab, listing) => {
|
||||
const { description, geolocation, price, title, publicData } = listing.attributes;
|
||||
const images = listing.images;
|
||||
|
||||
switch (tab) {
|
||||
case DESCRIPTION:
|
||||
return !!(description && title);
|
||||
case FEATURES:
|
||||
return !!(publicData && publicData.amenities);
|
||||
case CAPACITY:
|
||||
return !!(publicData && publicData.capacity);
|
||||
case POLICY:
|
||||
return !!(publicData && typeof publicData.rules !== 'undefined');
|
||||
case LOCATION:
|
||||
return !!(geolocation && publicData && publicData.location && publicData.location.address);
|
||||
case PRICING:
|
||||
return !!price;
|
||||
case PHOTOS:
|
||||
return images && images.length > 0;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Next task is to add form and panel components that render the capacity tab. As for the form, let's
|
||||
create a `EditListingCapacityForm` component:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { arrayOf, bool, func, shape, string } from 'prop-types';
|
||||
import { compose } from 'redux';
|
||||
import { Form as FinalForm } from 'react-final-form';
|
||||
import { intlShape, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import { propTypes } from '../../util/types';
|
||||
import { Form, Button, FieldSelect } from '../../components';
|
||||
import { required } from '../../util/validators';
|
||||
|
||||
import css from './EditListingCapacityForm.css';
|
||||
|
||||
export const EditListingCapacityFormComponent = props => (
|
||||
<FinalForm
|
||||
{...props}
|
||||
render={fieldRenderProps => {
|
||||
const {
|
||||
className,
|
||||
disabled,
|
||||
handleSubmit,
|
||||
intl,
|
||||
invalid,
|
||||
pristine,
|
||||
saveActionMsg,
|
||||
updated,
|
||||
updateError,
|
||||
updateInProgress,
|
||||
capacityOptions,
|
||||
} = fieldRenderProps;
|
||||
|
||||
const capacityPlaceholder = intl.formatMessage({
|
||||
id: 'EditListingCapacityForm.capacityPlaceholder',
|
||||
});
|
||||
|
||||
const errorMessage = updateError ? (
|
||||
<p className={css.error}>
|
||||
<FormattedMessage id="EditListingCapacityForm.updateFailed" />
|
||||
</p>
|
||||
) : null;
|
||||
|
||||
const capacityRequired = required(
|
||||
intl.formatMessage({
|
||||
id: 'EditListingCapacityForm.capacityRequired',
|
||||
})
|
||||
);
|
||||
|
||||
const classes = classNames(css.root, className);
|
||||
const submitReady = updated && pristine;
|
||||
const submitInProgress = updateInProgress;
|
||||
const submitDisabled = invalid || disabled || submitInProgress;
|
||||
|
||||
return (
|
||||
<Form className={classes} onSubmit={handleSubmit}>
|
||||
{errorMessage}
|
||||
|
||||
<FieldSelect
|
||||
className={css.capacity}
|
||||
name="capacity"
|
||||
id="capacity"
|
||||
validate={capacityRequired}
|
||||
>
|
||||
<option value="">{capacityPlaceholder}</option>
|
||||
{capacityOptions.map(c => (
|
||||
<option key={c.key} value={c.key}>
|
||||
{c.label}
|
||||
</option>
|
||||
))}
|
||||
</FieldSelect>
|
||||
|
||||
<Button
|
||||
className={css.submitButton}
|
||||
type="submit"
|
||||
inProgress={submitInProgress}
|
||||
disabled={submitDisabled}
|
||||
ready={submitReady}
|
||||
>
|
||||
{saveActionMsg}
|
||||
</Button>
|
||||
</Form>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
EditListingCapacityFormComponent.defaultProps = {
|
||||
selectedPlace: null,
|
||||
updateError: null,
|
||||
};
|
||||
|
||||
EditListingCapacityFormComponent.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
onSubmit: func.isRequired,
|
||||
saveActionMsg: string.isRequired,
|
||||
updated: bool.isRequired,
|
||||
updateError: propTypes.error,
|
||||
updateInProgress: bool.isRequired,
|
||||
capacityOptions: arrayOf(
|
||||
shape({
|
||||
key: string.isRequired,
|
||||
label: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
};
|
||||
|
||||
export default compose(injectIntl)(EditListingCapacityFormComponent);
|
||||
```
|
||||
|
||||
The form component receives `capacityOptions` as a prop which are used to populate a `FieldSelect`
|
||||
component for selecting the capacity. The `EditListingCapacityForm` is also added to the
|
||||
`src/containers/index.js` file so that it can easilly be referenced from other components. To use
|
||||
the capacity editing form we'll add a panel component which is then used in `EditListingWizardTab`
|
||||
to render the wizard phase. This component we'll call `EditListingCapacityPanel`:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { ensureOwnListing } from '../../util/data';
|
||||
import { ListingLink } from '../../components';
|
||||
import { EditListingCapacityForm } from '../../containers';
|
||||
import config from '../../config.js';
|
||||
|
||||
import css from './EditListingCapacityPanel.css';
|
||||
|
||||
const EditListingCapacityPanel = props => {
|
||||
const {
|
||||
className,
|
||||
rootClassName,
|
||||
listing,
|
||||
onSubmit,
|
||||
onChange,
|
||||
submitButtonText,
|
||||
panelUpdated,
|
||||
updateInProgress,
|
||||
errors,
|
||||
} = props;
|
||||
|
||||
const classes = classNames(rootClassName || css.root, className);
|
||||
const currentListing = ensureOwnListing(listing);
|
||||
const { publicData } = currentListing.attributes;
|
||||
|
||||
const panelTitle = currentListing.id ? (
|
||||
<FormattedMessage
|
||||
id="EditListingCapacityPanel.title"
|
||||
values={{ listingTitle: <ListingLink listing={listing} /> }}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage id="EditListingCapacityPanel.createListingTitle" />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<h1 className={css.title}>{panelTitle}</h1>
|
||||
<EditListingCapacityForm
|
||||
className={css.form}
|
||||
initialValues={{ capacity: publicData.capacity }}
|
||||
onSubmit={values => {
|
||||
const { capacity } = values;
|
||||
const updateValues = {
|
||||
publicData: {
|
||||
capacity,
|
||||
},
|
||||
};
|
||||
onSubmit(updateValues);
|
||||
}}
|
||||
onChange={onChange}
|
||||
saveActionMsg={submitButtonText}
|
||||
updated={panelUpdated}
|
||||
updateError={errors.updateListingError}
|
||||
updateInProgress={updateInProgress}
|
||||
capacityOptions={config.custom.capacityOptions}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { func, object, string, bool } = PropTypes;
|
||||
|
||||
EditListingCapacityPanel.defaultProps = {
|
||||
className: null,
|
||||
rootClassName: null,
|
||||
listing: null,
|
||||
};
|
||||
|
||||
EditListingCapacityPanel.propTypes = {
|
||||
className: string,
|
||||
rootClassName: string,
|
||||
|
||||
// We cannot use propTypes.listing since the listing might be a draft.
|
||||
listing: object,
|
||||
|
||||
onSubmit: func.isRequired,
|
||||
onChange: func.isRequired,
|
||||
submitButtonText: string.isRequired,
|
||||
panelUpdated: bool.isRequired,
|
||||
updateInProgress: bool.isRequired,
|
||||
errors: object.isRequired,
|
||||
};
|
||||
|
||||
export default EditListingCapacityPanel;
|
||||
```
|
||||
|
||||
In the panel component we check for an initial capacity value from the `public_data` property of the
|
||||
listing. In the submit handler the new capacity value is stored in the same property. The updated
|
||||
listing object is eventually passed to the `updateListingDraft` and `requestUpdateListing` functions
|
||||
in the `EditListingDetails.duck.js` file where the data updates are handled. Respectively
|
||||
`EditListingCapacityPanel` needs to be exported from `src/components/index.js` for easier access
|
||||
from other files.
|
||||
|
||||
Now that we have the panel and the form all ready we can add the panel to the `EditListingWizardTab`
|
||||
component. This is done by adding a new block to the `switch` structure that handles rendering the
|
||||
correct panel:
|
||||
|
||||
```js
|
||||
case CAPACITY: {
|
||||
const submitButtonTranslationKey = isNew
|
||||
? 'EditListingWizard.saveNewCapacity'
|
||||
: 'EditListingWizard.saveEditCapacity';
|
||||
return (
|
||||
<EditListingCapacityPanel
|
||||
{...panelProps(CAPACITY)}
|
||||
submitButtonText={intl.formatMessage({ id: submitButtonTranslationKey })}
|
||||
onSubmit={values => {
|
||||
onCompleteEditListingWizardTab(tab, values);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
There! Now we've extended the listing data model with capacity information.
|
||||
|
||||

|
||||
|
||||
The capacity data can now be added to new and existing listings. Next chapter describes how that
|
||||
data can be presented in the listing page.
|
||||
|
||||
## Show the attribute on listing page
|
||||
|
||||
Next step in adding a new attribute to the listing is to present it in the listing page. On some
|
||||
cases an extension to the listing data model can be used purely to enhance the application logic but
|
||||
in our case with the capacity information we want to show the added attribute in the listing page
|
||||
for potential customers.
|
||||
|
||||
To show the capacity attribute in the listing page, let's create a specific component for it and
|
||||
place it in the `ListingPage` container. Desired outcome could also be achieved just by editing the
|
||||
`ListingPage` but extracting the capacity UI parts into a separate component will simplify the
|
||||
`ListingPage` and make possible upstream updates from the Flex web template repo easier as there's
|
||||
less chances for merge conflicts. So, let's create a `SectionCapacity` component in the
|
||||
`src/containers/ListingPage/` directory:
|
||||
|
||||
```js
|
||||
import React from 'react';
|
||||
import { array, shape, string } from 'prop-types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import css from './ListingPage.css';
|
||||
|
||||
const SectionCapacity = props => {
|
||||
const { publicData, options } = props;
|
||||
|
||||
const capacity = publicData.capacity;
|
||||
const capacityOption = options.find(option => option.key === capacity);
|
||||
|
||||
return capacityOption ? (
|
||||
<div className={css.sectionCapacity}>
|
||||
<h2 className={css.capacityTitle}>
|
||||
<FormattedMessage id="ListingPage.capacityTitle" />
|
||||
</h2>
|
||||
<p className={css.capacity}>{capacityOption.label}</p>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
|
||||
SectionCapacity.propTypes = {
|
||||
options: array.isRequired,
|
||||
publicData: shape({
|
||||
capacity: string,
|
||||
}).isRequired,
|
||||
};
|
||||
|
||||
export default SectionCapacity;
|
||||
```
|
||||
|
||||
Remember to add corresponding css definitions to `ListingPage.css` to get the styling right. Import
|
||||
the component into `ListingPage` and place it inside the `<div className={css.mainContent}>`
|
||||
element:
|
||||
|
||||
```js
|
||||
import SectionCapacity from './SectionCapacity';
|
||||
```
|
||||
|
||||
```js
|
||||
<div className={css.mainContent}>
|
||||
{/* other sections */}
|
||||
|
||||
<SectionCapacity publicData={publicData} options={capacityOptions} />
|
||||
|
||||
{/* other sections */}
|
||||
</div>
|
||||
```
|
||||
|
||||
```js
|
||||
ListingPageComponent.defaultProps = {
|
||||
// other default props
|
||||
capacityOptions: config.custom.capacityOptions,
|
||||
};
|
||||
|
||||
ListingPageComponent.propTypes = {
|
||||
// other props
|
||||
capacityOptions: array,
|
||||
};
|
||||
```
|
||||
|
||||
In the snippet above, the capacity options are passed to the `ListingPage` as a property, with the
|
||||
default property value pulling the options from the custom config (`marketplace-custom-config.js`).
|
||||
This way the listing page test can define it's own capacity options that are in line with test data
|
||||
used in the test and custom config changes will not affect test results.
|
||||
|
||||
And voilà, we have listing capacity presented in the listing page!
|
||||
|
||||

|
||||
|
||||
## Use the attribute as a search filter
|
||||
|
||||
To see how the capacity attribute can be used to filter search results, please refer to the
|
||||
[search filters documentation](./search-filters.md).
|
||||
https://www.sharetribe.com/docs/guides/how-to-extend-listing-data-in-ftw/
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
# Extended Data
|
||||
|
||||
_Extended data_ is arbitrary information that can be attached to certain resources in the Flex API.
|
||||
Extended data values can be of any valid JSON data type, including JSON data structures like object
|
||||
and array. The purpose of extended data is to expand the ways in which the Flex API can be used.
|
||||
|
||||
## Types of extended data
|
||||
|
||||
Extended data is divided into two types: _public data_ and _protected data_.
|
||||
|
||||
### Public data
|
||||
|
||||
Public data can be written by anyone who has write permission to a resource and it is visible to
|
||||
anyone who can read that resource.
|
||||
|
||||
### Protected data
|
||||
|
||||
Protected data, just like public data, can be modified by anyone with write access to the resource.
|
||||
However, it can only be read by marketplace operators. It is also possible to reveal the protected
|
||||
data at a specific point in the booking transaction process, for example if listing's address is
|
||||
presented to the customer after a booking has been confirmed.
|
||||
|
||||
## Data schema and searched
|
||||
|
||||
Extended data works out of the box without any prior configuration to the marketplace. Public and
|
||||
protected data can be added to listings just by passing it in the create/update request to the API.
|
||||
However, optionally a _data schema_ can be defined for an extended data key. The data schema allows
|
||||
that extended data attribute to be used as a search parameter in some API endpoints, like in the
|
||||
listings query endpoint. Contact Sharetribe team if you need to add a schema to your extended data.
|
||||
|
||||
## Extended data in Flex template app
|
||||
|
||||
The Flex web template uses extended data by default with listing and user resources. In case of the
|
||||
listings, category and amenities information is stored in the `publicData` attribute. For the users,
|
||||
phone number is stored in the `protectedData` attribute. As for indexed data, the template app
|
||||
relies on data schemas for categories and amenities to be configured for the marketplace in the API
|
||||
as those values are used as filters in the listing search.
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
# Folder structure
|
||||
|
||||
After cloning the repo, your project should look like this:
|
||||
|
||||
```
|
||||
├── ext
|
||||
│ └── default-mail-templates
|
||||
├── package.json
|
||||
├── docs
|
||||
├── public
|
||||
│ ├── 500.html
|
||||
│ ├── index.html
|
||||
│ ├── robots.txt
|
||||
│ └── static
|
||||
├── server
|
||||
│ ├── index.js
|
||||
│ ├── csp.js
|
||||
│ ├── sitemap.js
|
||||
│ └ ...
|
||||
└── src
|
||||
├── Routes.js
|
||||
├── analytics
|
||||
├── app.js
|
||||
├── assets
|
||||
├── components
|
||||
├── config.js
|
||||
├── containers
|
||||
├── ducks
|
||||
├── examples.js
|
||||
├── forms
|
||||
├── index.js
|
||||
├── marketplace-custom-config.js
|
||||
├── marketplace.css
|
||||
├── marketplaceFonts.css
|
||||
├── marketplaceIndex.css
|
||||
├── reducers.js
|
||||
├── routeConfiguration.js
|
||||
├── store.js
|
||||
├── translations
|
||||
└── util
|
||||
```
|
||||
|
||||
## public/index.html
|
||||
|
||||
This is the page template. It includes fonts, Stripe SDK, Mapbox API (and Google Maps JavaScript
|
||||
API), and app icons. It also specifies placeholders for tags generated by
|
||||
[React Helmet](https://github.com/nfl/react-helmet)
|
||||
|
||||
## src/
|
||||
|
||||
* `src/index.js` is the JavaScript entry point on the client-side (it also exports `renderApp`
|
||||
function to be used for server-side rendering).
|
||||
* `src/app.js` creates ClientApp and ServerApp
|
||||
* `src/routeConfiguration.js` defines logic between routes and page-level components
|
||||
* `src/marketplace.css`, `src/marketplaceIndex.css`, `src/marketplaceIndex.css` define marketplace
|
||||
level styling using [CSS Properties](http://cssnext.io/features/#custom-properties-var) and CSS
|
||||
Property Sets.
|
||||
* `src/translations/en.json` all English translations used in Saunatime template app. (A good place
|
||||
to start customization.)
|
||||
|
||||
### `src/components/`
|
||||
|
||||
Normal components used in Flex Template for Web are defined here. The main file for the component is
|
||||
behind path `src/components/ComponentName/ComponentName.js`, but you can import it through
|
||||
`component/index.js`:
|
||||
|
||||
`import { NamedLink } from 'path/to/components';`
|
||||
|
||||
When creating new components, remember to import marketplace.css file to component's own CSS file -
|
||||
that way you can use CSS Property variables defined in `src/marketplace.css`.
|
||||
|
||||
### `src/containers/`
|
||||
|
||||
Containers are components that are connected to [Redux](https://redux.js.org/) store. Currently,
|
||||
this folder contains only page-level components and one common component: TopbarContainer.
|
||||
|
||||
### `src/forms/`
|
||||
|
||||
Forms are in their own folder since they are using a special library to handle validations, errors,
|
||||
internal state, etc.
|
||||
|
||||
## server/
|
||||
|
||||
* `server/index.js` handles server-side rendering (SSR). Built with [Express](http://expressjs.com)
|
||||
(Node.js framework).
|
||||
* `server/csp.js` [Content Security Policy](https://content-security-policy.com), whitelist of
|
||||
domains that the application and loaded scripts are allowed to access from the browser.
|
||||
* `server/sitemap.js` generates a minimal sitemap.xml for SEO purposes.
|
||||
|
||||
## ext/default-mail-templates/
|
||||
|
||||
This file contains default email templates for various needs (e.g. _booking-request-accepted_, or
|
||||
_verify-your-email_). You should customize all the different files (\*-html.html, \*-subject.txt,
|
||||
and \*-text.txt).
|
||||
|
||||
_**NOTE:** Changing these doesn't change actual email templates - you need to send customized
|
||||
templates to your Flex contact person._
|
||||
|
|
@ -1,181 +1,5 @@
|
|||
# Google Maps
|
||||
|
||||
Flex Template for Web (FTW) offers out of the box support for Google Maps API for showing a map and
|
||||
searching locations with search autocompletion. This document describes how to set up the API key
|
||||
for the API requests to work properly.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
> Note: before making the change to Google Maps, you should consider if you are OK with their
|
||||
> current pricing. There's a pricing calculator available in their
|
||||
> [pricing page](https://cloud.google.com/maps-platform/pricing/). FTW's default map provider is
|
||||
> Mapbox, which is often cheaper.
|
||||
> [Learn more about how to use Mapbox or some other map provider instead of Google Maps](https://github.com/sharetribe/flex-template-web/blob/e6034c7690c095553f44092b032689cd6b6f7546/docs/map-providers.md).
|
||||
|
||||
## Generate a Google Maps API key
|
||||
|
||||
Go to the
|
||||
[Google Maps JavaScript API V3 Reference](https://developers.google.com/maps/documentation/javascript/reference),
|
||||
click on the "GET A KEY" button in the top bar, and follow the instructions. You can copy the given
|
||||
key to your application now.
|
||||
|
||||
## Enable Google Places API Web Service
|
||||
|
||||
Follow the instructions in the
|
||||
[Getting started](https://developers.google.com/maps/documentation/javascript/places#GetStarted)
|
||||
section of the Places library documentation to enable using the Google Places API Web Service. Also
|
||||
Maps Static API and Maps Javascript API need to be enabled.
|
||||
|
||||
## Setup the application to use the API key
|
||||
|
||||
The application uses the `REACT_APP_GOOGLE_MAPS_API_KEY` environment variable for the key value. For
|
||||
local development, you can add the variable in the Gitignored `.env` file in the project root:
|
||||
|
||||
```
|
||||
REACT_APP_GOOGLE_MAPS_API_KEY=my-key-here
|
||||
```
|
||||
|
||||
## Setup common locations to reduce typing
|
||||
|
||||
The location autocomplete-input in the landing page and the topbar can be configured to have
|
||||
specific locations shown by default when the user focuses on the input and hasn't yet typed in any
|
||||
searches. This reduces the typing required for common searches and also reduces the need to use
|
||||
Google Map Places API that much.
|
||||
|
||||
To use default searches, another environment variable needs to be set:
|
||||
|
||||
```
|
||||
REACT_APP_DEFAULT_SEARCHES_ENABLED=true
|
||||
```
|
||||
|
||||
The default locations have been described in file:
|
||||
[src/default-location-searches.js](../src/default-location-searches.js).
|
||||
|
||||
The same environment variable also shows "current location" suggestion, which will make the browser
|
||||
to ask user's current location. This is a fast way to search listings nearby. You can specify
|
||||
whether to use the current location from [config.js](../src/config.js). Search for variables:
|
||||
`suggestCurrentLocation` and `currentLocationBoundsDistance`.
|
||||
|
||||
## Change components: use Google Map versions instead of Mapbox
|
||||
|
||||
If you wish to use Google Maps instead of Mapbox, you need to make some changes to FTW default
|
||||
setup.
|
||||
|
||||
### 1. Include Google Map script instead of Mapbox scripts
|
||||
|
||||
Mapbox related scripts can be removed from index.html and instead use Google Map script described in
|
||||
comments.
|
||||
|
||||
_public/index.html:_
|
||||
|
||||
```html
|
||||
<script src="%PUBLIC_URL%/static/scripts/mapbox/mapbox-sdk.min.js"></script>
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v0.47.0/mapbox-gl.css" rel="stylesheet" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v0.47.0/mapbox-gl.js"></script>
|
||||
<script>
|
||||
window.mapboxgl.accessToken = '%REACT_APP_MAPBOX_ACCESS_TOKEN%';
|
||||
</script>
|
||||
<!--
|
||||
If Google Maps is used instead of Mapbox, you need to include Google's script instead:
|
||||
<script src="https://maps.googleapis.com/maps/api/js?key=%REACT_APP_GOOGLE_MAPS_API_KEY%&libraries=places"></script>
|
||||
-->
|
||||
```
|
||||
|
||||
### 2. Searching with Google's geocoding API
|
||||
|
||||
Location search aka LocationAutocompleteInput should use Google Map specific geocoder. The correct
|
||||
import is written to the comments of LocationAutocompleteInput component.
|
||||
|
||||
_src/components/LocationAutocompleteInput/LocationAutocompleteInputImpl.js:_
|
||||
|
||||
```js
|
||||
import Geocoder, { GeocoderAttribution, CURRENT_LOCATION_ID } from './GeocoderMapbox';
|
||||
// import Geocoder, { GeocoderAttribution, CURRENT_LOCATION_ID } from './GeocoderGoogleMaps';
|
||||
```
|
||||
|
||||
Furthermore, Google Map states in their terms of service that Google logo needs to be visible when
|
||||
using their geocoding service. It is available as a background image below the autocomplete
|
||||
predictions. However, there needs to be enough padding for that logo. You can change the padding
|
||||
through `marketplace.css`.
|
||||
|
||||
_src/marketplace.css:_
|
||||
|
||||
```js
|
||||
/* Google Maps needs 72px bottom padding to accommodate logo, Mapbox doesn't have one */
|
||||
--locationAutocompleteBottomPadding: 8px;
|
||||
```
|
||||
|
||||
### 3. Show correct map on ListingPage (Map component)
|
||||
|
||||
Google Map version (containing both static and dynamic maps) can be taken into use by importing
|
||||
correct map subcomponent.
|
||||
|
||||
_src/components/Map/Map.js:_
|
||||
|
||||
```js
|
||||
import { StaticMap, DynamicMap, isMapsLibLoaded } from './MapboxMap';
|
||||
// import { StaticMap, DynamicMap, isMapsLibLoaded } from './GoogleMap';
|
||||
```
|
||||
|
||||
### 4. SearchMap.js
|
||||
|
||||
The most complex change is happening in SearchPage. First, you need to import `SearchMapWithMapbox`
|
||||
instead of `SearchMapWithGoogleMap`.
|
||||
|
||||
_src/components/SearchMap/SearchMap.js:_
|
||||
|
||||
Remove this:
|
||||
|
||||
```js
|
||||
import SearchMapWithMapbox, {
|
||||
LABEL_HANDLE,
|
||||
INFO_CARD_HANDLE,
|
||||
getMapBounds,
|
||||
getMapCenter,
|
||||
fitMapToBounds,
|
||||
isMapsLibLoaded,
|
||||
} from './SearchMapWithMapbox';
|
||||
```
|
||||
|
||||
And add this instead:
|
||||
|
||||
```js
|
||||
import SearchMapWithGoogleMap, {
|
||||
LABEL_HANDLE,
|
||||
INFO_CARD_HANDLE,
|
||||
getMapBounds,
|
||||
getMapCenter,
|
||||
fitMapToBounds,
|
||||
isMapsLibLoaded,
|
||||
} from './SearchMapWithGoogleMap';
|
||||
```
|
||||
|
||||
Then, in `render` method, you need to put `SearchMapWithGoogleMap` component into use by replacing
|
||||
`SearchMapWithMapbox` which is defined inside `ReusableMapContainer`. The component with correct
|
||||
props is already there in the comments:
|
||||
|
||||
```js
|
||||
// When changing from default map provider to Google Maps, you should use the following
|
||||
// component instead of SearchMapWithMapbox:
|
||||
//
|
||||
// <SearchMapWithGoogleMap
|
||||
// containerElement={
|
||||
// <div id="search-map-container" className={classes} onClick={this.onMapClicked} />
|
||||
// }
|
||||
// mapElement={<div className={mapRootClassName || css.mapRoot} />}
|
||||
// bounds={bounds}
|
||||
// center={center}
|
||||
// location={location}
|
||||
// infoCardOpen={infoCardOpen}
|
||||
// listings={listings}
|
||||
// activeListingId={activeListingId}
|
||||
// mapComponentRefreshToken={this.state.mapReattachmentCount}
|
||||
// createURLToListing={this.createURLToListing}
|
||||
// onListingClicked={this.onListingClicked}
|
||||
// onListingInfoCardClicked={this.onListingInfoCardClicked}
|
||||
// onMapLoad={this.onMapLoadHandler}
|
||||
// onMapMoveEnd={onMapMoveEnd}
|
||||
// zoom={zoom}
|
||||
// />
|
||||
```
|
||||
|
||||
The only extra step is to make `mapRootClassName` property available from `this.props` at the
|
||||
beginning of the `render` method.
|
||||
https://www.sharetribe.com/docs/guides/how-to-use-google-maps-in-ftw/
|
||||
|
|
|
|||
|
|
@ -1,47 +1,5 @@
|
|||
# Icons
|
||||
|
||||
The default icons are generated with [RealFaviconGenerator](https://realfavicongenerator.net/). You
|
||||
can upload your original icon to the tool, customize the colors and themes, and download a generated
|
||||
set if icons and an HTML snippet to point to those images.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
1. Open https://realfavicongenerator.net/
|
||||
|
||||
1. Upload your original icon image
|
||||
|
||||
1. Configure platform specific icons
|
||||
|
||||
**Note:** Remember to set the "Theme color" in the Android Chrome section
|
||||
|
||||
1. Configure the paths to use `/static/icons/` as the root path of the icons
|
||||
|
||||
1. Generate the icons
|
||||
|
||||
1. Unzip the favicons.zip archive and replace the default icons and files in
|
||||
[public/static/icons/](../public/static/icons/) with the new icons
|
||||
|
||||
1. Replace the default HTML snippet in [public/index.html](../public/index.html) with the snippet
|
||||
from the generator.
|
||||
|
||||
**Note:** Remove the manifest link from the snippet as we have a default manifest with extra
|
||||
data compared to the generated one. You can edit the default file as you wish.
|
||||
|
||||
**Example HTML snippet:**
|
||||
|
||||
```html
|
||||
<!-- Start Favicons from https://realfavicongenerator.net/ -->
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/icons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/icons/favicon-16x16.png">
|
||||
<link rel="mask-icon" href="/static/icons/safari-pinned-tab.svg" color="#c0392b">
|
||||
<link rel="shortcut icon" href="/static/icons/favicon.ico">
|
||||
<meta name="msapplication-config" content="/static/icons/browserconfig.xml">
|
||||
<meta name="theme-color" content="#c0392b">
|
||||
<!-- End Favicons -->
|
||||
```
|
||||
|
||||
## Map marker icon
|
||||
|
||||
The map marker icon in the listing can be found in
|
||||
[src/components/Map/images/marker-32x32.png](../src/components/Map/images/marker-32x32.png). The
|
||||
dimensions should be 32x32 pixels, so the `favicon-32x32.png` file can be used to replace the map
|
||||
icon.
|
||||
https://www.sharetribe.com/docs/guides/how-to-change-ftw-icons/
|
||||
|
|
|
|||
|
|
@ -1,74 +1,5 @@
|
|||
# Improving page rendering performance
|
||||
|
||||
When we think about page speed there are actually two different scenarios that we need to address:
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
* The speed of initial page load and possible reloads after that
|
||||
* The speed of changing the page within Single page application (SPA)
|
||||
|
||||
The first one is usually a slower process. A browser needs to load all the HTML, CSS, JavaScript,
|
||||
and images - and then it needs to understand and execute those files, calculate layout, paint
|
||||
components and finally composite the whole view. The initial page load is the slowest since the
|
||||
consequent page reloads can take benefit from browser caches.
|
||||
|
||||
SPAs can improve from that since they don't necessarily need to download anymore JavaScript, HTML,
|
||||
or CSS - already downloaded JavaScript might be enough for rendering consequent pages when a user
|
||||
wants to navigate to another page. Most of the time SPAs just fetch data for that page.
|
||||
|
||||
These two UX scenarios might also conflict with each other. If all the JavaScript is in one big
|
||||
bundle, page changes within a SPA are fast. However, downloading and evaluating a big JavaScript
|
||||
file is slowing initial page rendering down. Even though users rarely experience the full initial
|
||||
page load speed when they use an SPA like Flex Template for Web, it is good to keep track of that
|
||||
speed. Especially since that is what search engine bots are experiencing and therefore it might
|
||||
affect your page rank.
|
||||
|
||||
Read more about
|
||||
[website performance](https://developers.google.com/web/fundamentals/performance/why-performance-matters/).
|
||||
|
||||
We haven't yet implemented code splitting to reduce initial page rendering time, but there're other
|
||||
improvements that could be done to improve both cases of page rendering.
|
||||
|
||||
## Check page performance
|
||||
|
||||
The first step is, of course, to start measuring performance.
|
||||
[Lighthouse](https://developers.google.com/web/tools/lighthouse/) is a good tool to check rendering
|
||||
performance. At least check those pages that are visible to unauthenticated users (e.g. landing
|
||||
page, search page, listing page, about page and other static pages).
|
||||
|
||||
Lighthouse will give you some tips about how to improve performance and other aspects that website
|
||||
developers should think about.
|
||||
|
||||
## Optimize image sizes
|
||||
|
||||
If your page is showing images, you should check that the image size is not bigger than what is
|
||||
needed. So, adjusting image dimensions is the first step, but you should also think about image
|
||||
quality, advanced rendering options and possibly serving those images from CDN instead of from
|
||||
within your web app.
|
||||
|
||||
Quick checklist:
|
||||
|
||||
* Check that the actual dimensions of an image match with DOM element's dimensions.
|
||||
* Lighthouse suggests that image compression level should be 85% or lower.
|
||||
[Read more](https://developers.google.com/web/tools/lighthouse/audits/optimize-images)
|
||||
* Good rule-of-thumb is that use JPEG for images and photos, where PNG is better for graphics, such
|
||||
as logos, graphs and illustrations.
|
||||
* If you are using JPEG images, think about saving them as progressive JPEGs.
|
||||
[Read more](https://cloudinary.com/blog/progressive_jpegs_and_green_martians) +
|
||||
[Photoshop guide](https://helpx.adobe.com/photoshop-elements/using/optimizing-images-jpeg-format.html)
|
||||
* If you are using PNG images, consider running them through PNG optimizers to reduce file size.
|
||||
Plenty of options available, one example is [TinyPNG.com](https://tinypng.com)
|
||||
* Think about serving images and other static assets from some CDN.
|
||||
[Read more.](https://www.smashingmagazine.com/2017/04/content-delivery-network-optimize-images/)
|
||||
|
||||
## Lazy load off-screen images and other components
|
||||
|
||||
Another way of dealing with images is to lazy load those images that are not visible inside an
|
||||
initially rendered part of the screen. Lazy loading these off-screen images can be done with helper
|
||||
function: `lazyLoadWithDimensions` (from `util/contextHelpers/`). Check `SectionLocations` component
|
||||
for details.
|
||||
|
||||
## Use sparse fields
|
||||
|
||||
Another way to reduce the amount of data that is fetched from API is sparse fields. This is a
|
||||
relatively new feature and Flex template app has not yet leveraged it fully, but it is created to
|
||||
reduce unnecessary data and speed up rendering. You can read more from
|
||||
[Flex API docs](https://flex-docs.sharetribe.com/#sparse-attributes).
|
||||
https://www.sharetribe.com/docs/guides/how-to-improve-performance/
|
||||
|
|
|
|||
|
|
@ -1,70 +1,5 @@
|
|||
# Integrating to map providers
|
||||
|
||||
Choice of map provider can significantly impact your costs. Flex Template for Web (FTW) originally
|
||||
supported only [Google Maps](./google-maps.md) out of the box, but after Google increased the
|
||||
pricing of its APIs a lot, the default provider was changed to Mapbox. The template now supports
|
||||
both Mapbox and Google Maps, and the map library used by Mapbox is used also by several other map
|
||||
providers, so integrating new providers that support this is rather easy (see instructions at the
|
||||
end of this doc).
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Setting up the Mapbox integration (the default map provider)
|
||||
|
||||
### 1. Generate a Mapbox access key
|
||||
|
||||
Sign up for a Mapbox and go to [account page](https://www.mapbox.com/account/). Then click
|
||||
`Create access token`.
|
||||
|
||||
> Read more about
|
||||
> [access tokens and consider rotating them](https://www.mapbox.com/help/how-access-tokens-work/).
|
||||
|
||||
### 2. Setup the application to use the access key
|
||||
|
||||
The application uses the `REACT_APP_MAPBOX_ACCESS_TOKEN` environment variable for the key value. For
|
||||
local development, you can add the variable in the Gitignored `.env` file in the project root:
|
||||
|
||||
```
|
||||
REACT_APP_MAPBOX_ACCESS_TOKEN=my-access-token-here
|
||||
```
|
||||
|
||||
### 3. Setup common locations to reduce typing
|
||||
|
||||
The location autocomplete input in the landing page and the topbar can be configured to have
|
||||
specific locations shown by default when the user focuses on the input and hasn't yet typed in any
|
||||
searches. This reduces the typing required for common searches and also reduces the need to use
|
||||
Mapbox geolocation API that much.
|
||||
|
||||
To use default searches, another environment variable needs to be set:
|
||||
|
||||
```
|
||||
REACT_APP_DEFAULT_SEARCHES_ENABLED=true
|
||||
```
|
||||
|
||||
The default locations are described in
|
||||
[src/default-location-searches.js](../src/default-location-searches.js).
|
||||
|
||||
The same environment variable also shows "current location" suggestion, which will make the browser
|
||||
to ask user's current location. This is a fast way to search listings nearby. You can specify
|
||||
whether to use the current location from [config.js](../src/config.js). Search for variables:
|
||||
`suggestCurrentLocation` and `currentLocationBoundsDistance`.
|
||||
|
||||
### 4. Check rare default configurations
|
||||
|
||||
Mapbox geocoding API doesn't always return bounding boxes for locations. Without bounding box
|
||||
SearchMap component can't adjust zoom level right for that particular place. Therefore there are
|
||||
default bounding boxes defined to different place types in
|
||||
[Mapbox specific geocoder](../src/components/LocationAutocompleteInput/GeocoderMapbox.js).
|
||||
|
||||
## Changing the map providers
|
||||
|
||||
### How to change from Mapbox to Google Maps
|
||||
|
||||
It is possible to use Google Map instead of the default map provider. Read more from
|
||||
[Google Map setup guide](./google-maps.md)
|
||||
|
||||
### How to use other map providers
|
||||
|
||||
The default map setup of FTW uses library called `mapbox-gl-js`. It is supported by quite many other
|
||||
map providers too. Thus, if you wish to use a map provider other than Google Maps or Mapbox, first
|
||||
check if the map provider you are considering is supporting this library. If they are, the change
|
||||
might be quite easy. Note: if you change the map tile provider you should also change geocoding API
|
||||
too (i.e. the API endpoint for `LocationAutocompleteInput` component).
|
||||
https://www.sharetribe.com/docs/guides/how-to-set-up-mapbox-for-ftw/
|
||||
|
|
|
|||
5
docs/redux.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Redux configuration
|
||||
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
https://www.sharetribe.com/docs/background/ftw-redux/
|
||||
5
docs/routing.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Routing
|
||||
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
https://www.sharetribe.com/docs/background/ftw-routing/
|
||||
|
|
@ -1,174 +1,5 @@
|
|||
# Search filters
|
||||
|
||||
The search experience can be improved by adding search filters to narrow down the results. The
|
||||
filters rely on listing's indexed data.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Filter types
|
||||
|
||||
The Flex template for web has two different filter types: _select single_ and _select multiple_. The
|
||||
_select single_ one can be used to filter out search result with only one value per search
|
||||
parameter. The _select multiple_ filters on the other hand can take multiple values for a single
|
||||
search parameter.
|
||||
|
||||
These two filter types are implemented with four different components, a standard and a plain one:
|
||||
|
||||
* Select single filter: `SelectSingleFilter` and `SelectSingleFilterPlain`
|
||||
* Select multiple filter: `SelectMultipleFilter` and `SelectMultipleFilterPlain`
|
||||
|
||||
The `SelectSingleFilter` and `SelectMultipleFilter` components are rendered as standard dropdowns in
|
||||
the search view. The plain filter components `SelectSingleFilterPlain` and
|
||||
`SelectMultipleFilterPlain` are used with `SearchFiltersMobile` and `SearchFiltersPanel` to provider
|
||||
filters in mobile view or to open filters in a distinct panel in order to fit more filters to the
|
||||
desktop search view.
|
||||
|
||||
## Adding a new search filter
|
||||
|
||||
Next we'll guide you through the steps of adding a _capacity_ filter to the marketplace.
|
||||
|
||||
First step for adding a new filter is to make sure that the data being used for filtering is saved
|
||||
in the listing's `publicData` attribute. On how to achieve this, please refer to the
|
||||
[documentation on extending the listing data model](./extend-listing.md). Another aspect in search
|
||||
filters is that the public data needs to be indexed in the API. This is currently achieved with a
|
||||
manual operation done by the Sharetribe support. Once a public data attribute is added to the
|
||||
listings and the data attribute is indexed, the listing searches can be filtered by that attribute
|
||||
by adding a query parameter that consists of a preceding "pub\_" and the attribute name, so for the
|
||||
_capacity_ attribute the parameter would be "pub_capacity".
|
||||
|
||||
Further reading on public data can be found in the
|
||||
[extended data documentation](./extended-data.md).
|
||||
|
||||
**NOTE:** Please contact the Sharetribe support in order to update your listing index schema when
|
||||
planning to use new listing fields as search filters.
|
||||
|
||||
### Common changes
|
||||
|
||||
A few common changes are required to add a select single or a select multiple filter to desktop and
|
||||
mobile views.
|
||||
|
||||
First of all, the value options for the filter need to be defined. One handy place to store these is
|
||||
the `marketplace-custom-config.js` file. The correct format is a list of objects with `key` and
|
||||
`label` fields:
|
||||
|
||||
```js
|
||||
export const capacityOptions = [
|
||||
{
|
||||
key: 'oneToThree',
|
||||
label: '1 to 3',
|
||||
},
|
||||
{
|
||||
key: 'fourToSix',
|
||||
label: '4 to 6',
|
||||
},
|
||||
...
|
||||
];
|
||||
```
|
||||
|
||||
A few changes need to be made to the `SearchPage` container in order to get the filters to work.
|
||||
|
||||
`SearchPage` needs the filter options. One handy way is to add the options as a prop to the
|
||||
component and then set `defaultProps` value from `config.custom` (contains the
|
||||
`marketplace-custom-config.js` exports). This way tests can pass in their own values for the filter
|
||||
options and filter option changes won't affect tests.
|
||||
|
||||
```js
|
||||
SearchPageComponent.defaultProps = {
|
||||
// other default props
|
||||
capacityOptions: config.custom.capacityOptions,
|
||||
};
|
||||
|
||||
SearchPageComponent.propTypes = {
|
||||
// other props
|
||||
capacityOptions: array,
|
||||
};
|
||||
```
|
||||
|
||||
Also a filter configuration needs to be added to the object returned by the `filters` method. The
|
||||
`filters` method combines query param name and options information of each filter so that those can
|
||||
be passed on to subcomponents and used for validating the filter values and rendering the filter
|
||||
components.
|
||||
|
||||
To add a filter configuration, extract the filter options from the props and set the param name as
|
||||
the one defined in your extended data indexing configuration:
|
||||
|
||||
```js
|
||||
filters() {
|
||||
const { capacityOptions } = this.props;
|
||||
|
||||
return {
|
||||
capacityFilter: {
|
||||
paramName: 'pub_capacity',
|
||||
options: capacityOptions,
|
||||
},
|
||||
...
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
Final thing to do in `SearchPage` is to pass the filters configuration on to the components that
|
||||
take care of rendering the filters. This is achieved by `primaryFilters` and `secondaryFilters`
|
||||
props that are passed to `MainPanel`. The configurations are passed as an object in the same form as
|
||||
the configuration object in `filters`.
|
||||
|
||||
```js
|
||||
<MainPanel
|
||||
...
|
||||
primaryFilters={{
|
||||
categoryFilter: filters.categoryFilter,
|
||||
amenitiesFilter: filters.amenitiesFilter,
|
||||
}}
|
||||
secondaryFilters={{
|
||||
capacityFilter: filters.capacityFilter,
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
||||
These props are used for resolving filter states and validating the filter values. The contents are
|
||||
passed as props to the components that render the filters: `SearchFilters`, `SearchFiltersPanel` and
|
||||
`SearchFiltersMobile` .The difference between filters passed as primary and secondary varies in
|
||||
mobile and desktop views:
|
||||
|
||||
* **Desktop:** Primary filters are shown in the top of the search view, secondary filters are
|
||||
rendered in a distinct panel that opens on top of search results.
|
||||
* **Mobile:** Both primary and secondary filters are rendered in the same modal.
|
||||
|
||||
### Desktop filters
|
||||
|
||||

|
||||
|
||||
A basic desktop filter that renders as a dropdown button on top of the search results panel is
|
||||
achieved using the `SelectSingleFilter` and `SelectMultipleFilter` components. To add standard
|
||||
desktop filters, perform the following in `SearchFilters` component:
|
||||
|
||||
* declare a prop with the same name that you added the filter config to `primaryFilters`
|
||||
* resolve the filters initial value with `initialValue` and `initialValues` functions
|
||||
* render the filter by using a `SelectSingleFilter` or `SelectMultipleFilter` component inside the
|
||||
`<div className={css.filters}>` element
|
||||
|
||||
### Desktop filters panel
|
||||
|
||||

|
||||
|
||||
If more filters are required than can fit into the top filter bar, the `SearchFiltersPanel`
|
||||
component can be used. It renders as a button in the top filter bar that opens a new panel that can
|
||||
contain a set of filters.
|
||||
|
||||
To use the `SearchFiltersPanel`, do the following:
|
||||
|
||||
* declare a prop with the same name that you added the filter config to `secondaryFilters`
|
||||
* resolve the filters initial value with `initialValue` and `initialValues` methods
|
||||
* use the `SelectSingleFilterPlain` and `SelectMultipleFilterPlain` components inside the
|
||||
`<div className={css.filtersWrapper}>` element to render the filters
|
||||
|
||||
### Mobile filters
|
||||
|
||||

|
||||
|
||||
The mobile view uses the same `SelectSingleFilterPlain` and `SelectMultipleFilterPlain` components
|
||||
as the filter panel. In this case the filter components are declared in `SearchFiltersMobile`. The
|
||||
following steps are required to add a mobile filter:
|
||||
|
||||
* declare a prop with the same name that you added the filter config to `primaryFilters` or
|
||||
`secondaryFilters`
|
||||
* resolve the filters initial value with `initialValue` and `initialValues` methods
|
||||
* use the `SelectSingleFilterPlain` and `SelectMultipleFilterPlain` components inside the
|
||||
`<div className={css.filtersWrapper}>` element to render the filters
|
||||
https://www.sharetribe.com/docs/guides/how-to-change-search-filters-in-ftw/
|
||||
|
|
|
|||
|
|
@ -1,38 +1,5 @@
|
|||
# Error logging with Sentry
|
||||
|
||||
The Flex template application supports error logging with [Sentry](https://sentry.io/) out of the
|
||||
box provided that required environment variables are set in place. Other logging solutions can also
|
||||
be used but the Sentry client comes already strapped into application.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Setting up Sentry keys
|
||||
|
||||
To enable the Sentry error logging a DSN, _Data Source Name_ has to be provided as an environment
|
||||
variable. Browser and Node environments both require their own keys. The key names are as follows:
|
||||
|
||||
* **SERVER_SENTRY_DSN** - the private Sentry DSN, used on the server side
|
||||
* **REACT_APP_PUBLIC_SENTRY_DSN** - the public Sentry DSN, used in the browser
|
||||
|
||||
The DSN keys can be aquired from the Sentry project settings. To test them in your local environment
|
||||
they can be passed for example to the `yarn run dev-server` command:
|
||||
|
||||
REACT_APP_PUBLIC_SENTRY_DSN='<public-sentry-dsn>' SERVER_SENTRY_DSN='<private-sentry-dsn>' yarn run dev-server
|
||||
|
||||
If the Sentry DSN keys are not provided the template app will log errors to the console. The logging
|
||||
and Sentry setup is implemented in [util/log.js](../src/util/log.js) and
|
||||
[server/log.js](../server/log.js) so refer to those files to change the external logging service or
|
||||
the logging logic in general.
|
||||
|
||||
## Source map retrieval
|
||||
|
||||
By default Sentry fetches the source maps for minified javascript files. However, this might now
|
||||
work in case some sort of authentication, e.g. basic auth, is required to reach your application. To
|
||||
circumvent this a security token can be configured to Sentry. Sentry then adds this token to the
|
||||
source maps request header so that the server hosting the marketplace can then be configured to let
|
||||
those requests access the source maps.
|
||||
|
||||
For basic auth the security token can be configured as follows:
|
||||
|
||||
In _Project > Settings > General > Client Security_ set the following values
|
||||
|
||||
* **Security token** - `Basic <your username:password in Base 64>`
|
||||
* **Security token header** - `Authorization`
|
||||
https://www.sharetribe.com/docs/guides/how-to-set-up-sentry-to-log-errors-in-ftw/
|
||||
|
|
|
|||
|
|
@ -1,258 +1,5 @@
|
|||
# Static pages
|
||||
|
||||
If you want to create simple pages that just show static content without need for data fetches, you
|
||||
can create a static page.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
Steps for creating a static page:
|
||||
|
||||
1. [Create a new folder under `src/containers/`](#1-creating-a-new-folder)
|
||||
2. [Create a new JavaScript file using the same name.](#2-creating-a-javascript-file)
|
||||
3. [Create a new CSS file using the same name.](#3-creating-a-css-file)
|
||||
4. [Write the content to a JavaScript file (i.e. AboutPage.js in our example).](#4-creating-the-component)
|
||||
5. [Write the style rules to CSS file (i.e. AboutPage.css in our example).](#5-adding-some-styles-to-the-css-file)
|
||||
6. [Add the newly created page component to `src/containers/index.js`](#6-adding-the-component-to-the-component-directory)
|
||||
7. [Add the newly created page to `src/routeConfiguration.js`](#7-adding-a-route-to-the-page)
|
||||
|
||||
## 1. Creating a new folder
|
||||
|
||||
Create a new folder under `src/containers/` with the name of your static page. E.g. "about" page
|
||||
should be named as `AboutPage`.
|
||||
|
||||
## 2. Creating a JavaScript file
|
||||
|
||||
Create a new JavaScript file using the folder name. The path should look like
|
||||
`src/containers/AboutPage/AboutPage.js`.
|
||||
|
||||
## 3. Creating a CSS file
|
||||
|
||||
Create a new CSS file using the folder name. The path should look like
|
||||
`src/containers/AboutPage/AboutPage.css`.
|
||||
|
||||
## 4. Creating the component
|
||||
|
||||
Template for a single column static page (AboutPage.js): (We'll go through this line-by-line below.)
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { StaticPage, TopbarContainer } from '../../containers';
|
||||
import {
|
||||
LayoutSingleColumn,
|
||||
LayoutWrapperTopbar,
|
||||
LayoutWrapperMain,
|
||||
LayoutWrapperFooter,
|
||||
Footer,
|
||||
NamedLink,
|
||||
ExternalLink,
|
||||
} from '../../components';
|
||||
|
||||
import css from './AboutPage.css';
|
||||
import image from './path/to/image.png';
|
||||
|
||||
const AboutPage = () => {
|
||||
return (
|
||||
<StaticPage
|
||||
className={css.root}
|
||||
title="About"
|
||||
schema={{
|
||||
'@context': 'http://schema.org',
|
||||
'@type': 'AboutPage',
|
||||
description: 'Description of this page',
|
||||
name: 'About page',
|
||||
}}
|
||||
>
|
||||
<LayoutSingleColumn>
|
||||
<LayoutWrapperTopbar>
|
||||
<TopbarContainer />
|
||||
</LayoutWrapperTopbar>
|
||||
<LayoutWrapperMain>
|
||||
<h1>Some content</h1>
|
||||
<img src={image} alt="My first ice cream." />
|
||||
<div>
|
||||
<NamedLink name="LandingPage">Go to home page</NamedLink> or
|
||||
<ExternalLink href="https://google.com">Go to Google</ExternalLink>
|
||||
</div>
|
||||
</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter>
|
||||
<Footer />
|
||||
</LayoutWrapperFooter>
|
||||
</LayoutSingleColumn>
|
||||
</StaticPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default AboutPage;
|
||||
```
|
||||
|
||||
We are using [React](https://reactjs.org/) and [JSX](https://reactjs.org/docs/introducing-jsx.html)
|
||||
to create components and pages. Therefore, we need to import React to our new component which is
|
||||
done in the first line.
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
```
|
||||
|
||||
In the second line we are importing two containers:
|
||||
|
||||
* `StaticPage`: helps in creating static pages
|
||||
* `TopbarContainer`: creates our Topbar component and fetches the data it needs.
|
||||
|
||||
```jsx
|
||||
import { StaticPage, TopbarContainer } from '../../containers';
|
||||
```
|
||||
|
||||
After that we need to import some components:
|
||||
|
||||
* `LayoutSingleColumn` and wrappers that it needs to position content
|
||||
* `Footer` component (to be added inside LayoutWrapperFooter)
|
||||
* `NamedLink` makes it easier to point to different pages inside the application
|
||||
* `ExternalLink` can be used to link outside the application. It creates a normal `<a>`link with
|
||||
extra attributes `target="_blank" rel="noopener noreferrer"` that add some security to these
|
||||
outbound links.
|
||||
|
||||
`LayoutSingleColumn` (and other layouts like LayoutSideNavigation) need to understand what the
|
||||
content is about. Therefore, different parts of the page need to be wrapped with specific
|
||||
components - in this case: `LayoutWrapperTopbar`, `LayoutWrapperMain`, and `LayoutWrapperFooter`.
|
||||
|
||||
```jsx
|
||||
import {
|
||||
LayoutSingleColumn,
|
||||
LayoutWrapperTopbar,
|
||||
LayoutWrapperMain,
|
||||
LayoutWrapperFooter,
|
||||
Footer,
|
||||
NamedLink,
|
||||
ExternalLink,
|
||||
} from '../../components';
|
||||
```
|
||||
|
||||
Then we need to import styles and possible other files from current folder. With CSS we are using
|
||||
[CSS Modules](https://github.com/css-modules/css-modules) to tackle possible classhes of different
|
||||
class names. [Read more.](#5-creating-the-css-file)
|
||||
|
||||
```jsx
|
||||
import css from './AboutPage.css';
|
||||
```
|
||||
|
||||
Then we also import an image which is used later (`<img src={image} alt="My first ice cream." />`).
|
||||
|
||||
```jsx
|
||||
import image from './path/to/image.png';
|
||||
```
|
||||
|
||||
Then after all the imports we are finally getting into phase were we define the component.
|
||||
`const AboutPage = props => { return <div></div>}` defines a component called AboutPage with content
|
||||
defined in return part. This is a
|
||||
[functional component](https://reactjs.org/docs/components-and-props.html).
|
||||
|
||||
In the template above we are using StaticPage component with some attributes:
|
||||
|
||||
```jsx
|
||||
<StaticPage
|
||||
className={css.root}
|
||||
title="About"
|
||||
schema={{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "AboutPage",
|
||||
"description": "Description of this page",
|
||||
"name": "About page",
|
||||
}}
|
||||
>
|
||||
```
|
||||
|
||||
* `className` is JSX name for `class` attribute used in plain HTML.
|
||||
* `title="About"` creates `<title>About</title>` element to `<head>` section of the page. (That
|
||||
title is also used in OpenGraph meta tags). You could also add
|
||||
`description="This is about page description"`
|
||||
* Then we have `schema` tag that defines some data for search engines in JSON-LD format. Check
|
||||
[schema.org](http://schema.org/docs/full.html) for more information.
|
||||
|
||||
Inside `StaticPage` component we define layout (`LayoutSingleColumn`) and add other components
|
||||
inside semantic content wrappers so that the layout is able to understand where to render those
|
||||
blocks.
|
||||
|
||||
```jsx
|
||||
<LayoutSingleColumn>
|
||||
<LayoutWrapperTopbar>
|
||||
<TopbarContainer />
|
||||
</LayoutWrapperTopbar>
|
||||
<LayoutWrapperMain>
|
||||
<h1>Some content</h1>
|
||||
<img src={image} alt="My first ice cream." />
|
||||
<div>
|
||||
<NamedLink name="LandingPage">Go to home page</NamedLink> or
|
||||
<ExternalLink href="https://google.com">Go to Google</ExternalLink>
|
||||
</div>
|
||||
</LayoutWrapperMain>
|
||||
<LayoutWrapperFooter>
|
||||
<Footer />
|
||||
</LayoutWrapperFooter>
|
||||
</LayoutSingleColumn>
|
||||
```
|
||||
|
||||
And as a final step we need to export the component. `export default AboutPage;`. See more from
|
||||
[babeljs.org](https://babeljs.io/learn-es2015/#ecmascript-2015-features-modules)
|
||||
|
||||
## 5. Adding some styles to the CSS file
|
||||
|
||||
Here's an example what your AboutPage.css file could look like:
|
||||
|
||||
```css
|
||||
@import '../../marketplace.css';
|
||||
|
||||
.root {
|
||||
padding: 24px;
|
||||
|
||||
/* Use CSS variable imported from marketplace.css */
|
||||
background-color: var(--marketplaceColor);
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Adding the component to the component directory
|
||||
|
||||
New component needs to be added to `src/containers/index.js` file or if it's a presentational
|
||||
component (not page or form) it should be inside components folder and therefore added to
|
||||
`src/components/index.js`
|
||||
|
||||
Inside that index.js you need to add line
|
||||
`export { default as AboutPage } from './AboutPage/AboutPage';`. This helps other parts of the app
|
||||
to import new components easily with `import { AboutPage } from '../../components'`.
|
||||
|
||||
## 7. Adding a route to the page
|
||||
|
||||
As a last step you need to add the newly created static page to the routing. This can be done in
|
||||
`src/routeConfiguration.js`.
|
||||
|
||||
Inside routeConfiguration function you should add a URL path, a page name (it should not conflicting
|
||||
with other pages), and the component itself.
|
||||
|
||||
Add it as first to the list of imported pages in alphabetical order (2nd line):
|
||||
|
||||
```jsx
|
||||
import {
|
||||
AboutPage,
|
||||
AuthenticationPage,
|
||||
CheckoutPage,
|
||||
```
|
||||
|
||||
and after that add the route configuration to your newly created page: (In this example we created
|
||||
about page so '/about' would work well as a path.)
|
||||
|
||||
```javascript
|
||||
{
|
||||
path: '/about',
|
||||
name: 'AboutPage',
|
||||
component: AboutPage,
|
||||
},
|
||||
```
|
||||
|
||||
## Read more
|
||||
|
||||
We are using several libraries in this example. If you want to read more, here's some pointers:
|
||||
|
||||
* [ES2015](https://babeljs.io/learn-es2015/): imports, exports, arrow functions
|
||||
* [React](https://reactjs.org/): for creating components
|
||||
* [JSX](https://reactjs.org/docs/introducing-jsx.html): for getting HTML-like markup syntax for own
|
||||
components
|
||||
* [CSS Modules](https://github.com/css-modules/css-modules)
|
||||
* [React Router](https://reacttraining.com/react-router/web/guides/philosophy): routing inside the
|
||||
application.
|
||||
https://www.sharetribe.com/docs/guides/how-to-add-static-pages-in-ftw/
|
||||
|
|
|
|||
274
docs/styling.md
|
|
@ -1,275 +1,5 @@
|
|||
# Flex Template for Web: styling
|
||||
|
||||
The goal for creating styling for Flex template app was to keep styling as close as possible to
|
||||
plain CSS while still trying to avoid the mess that comes along with globally defined cascading
|
||||
behavior that CSS is all about.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
To tackle this goal, we have split the styling into two levels in this template application:
|
||||
|
||||
* [Marketplace level styling](#marketplace-level-styling) through _marketplace.css_ (a kind of
|
||||
global theme)
|
||||
* [Component level styling](#styling-components) using
|
||||
[CSS Modules](https://github.com/css-modules/css-modules)
|
||||
|
||||
## Marketplace level styling
|
||||
|
||||
On top of functionalities provided by Create React App, we have
|
||||
[added a couple of extra libraries](https://www.npmjs.com/package/sharetribe-scripts#differences-to-react-scripts)
|
||||
to help to design consistent UIs faster. CSS Properties and CSS Property Sets are very useful for
|
||||
all kind of style sharing purposes, and we have created marketplace-level styling variables with
|
||||
them.
|
||||
|
||||
The concept behind CSS Properties is quite straightforward - they are variables that can be defined
|
||||
in root-element level and then used inside some CSS rule.
|
||||
|
||||
```css
|
||||
:root {
|
||||
--marketplaceColor: #ffff00;
|
||||
}
|
||||
|
||||
.linkToHomePage {
|
||||
color: var(--marketplaceColor);
|
||||
}
|
||||
```
|
||||
|
||||
(Read more about CSS Properties from [cssnext](http://cssnext.io/))
|
||||
|
||||
We have used this concept to create a marketplace-level "theming" that's defined in three files:
|
||||
_src/marketplace.css_, _src/marketplaceFonts.css_, and _marketplaceIndex.css_.
|
||||
|
||||
### marketplace.css
|
||||
|
||||
This is a good place to start customizing marketplace styles. For example, we define our color
|
||||
scheme here using CSS Property variables:
|
||||
|
||||
```css
|
||||
/* ================ Colors ================ */
|
||||
|
||||
--marketplaceColor: #c0392b;
|
||||
--marketplaceColorLight: #ff4c38;
|
||||
--marketplaceColorDark: #8c291e;
|
||||
|
||||
/* Used with inline CSS SVGs */
|
||||
--marketplaceColorEncoded: %23c0392b;
|
||||
|
||||
--successColor: #2ecc71;
|
||||
--successColorDark: #239954;
|
||||
--failColor: #ff0000;
|
||||
--attentionColor: #ffaa00;
|
||||
|
||||
--matterColorDark: #000000;
|
||||
--matterColor: #4a4a4a;
|
||||
--matterColorAnti: #b2b2b2;
|
||||
--matterColorNegative: #e7e7e7;
|
||||
--matterColorBright: #fcfcfc;
|
||||
--matterColorLight: #ffffff;
|
||||
```
|
||||
|
||||
Changing `--marketplaceColor: #c0392b;` to `--marketplaceColor: tomato;` will change the default
|
||||
marketplace color to tomato color. (It's a certain kind of red color.)
|
||||
|
||||
The `--marketplaceColorEncoded` variable holds the same value as `--marketplaceColor` but with the
|
||||
_#_ URL encoded. This value can be used to maintain a consistent color scheme with inline SVG icons.
|
||||
|
||||
`--successColor` (green) is used in form inputs for showing that the input value is valid.
|
||||
Sometimes, submit buttons (`<PrimaryButton>`) are also using that color to highlight the fact that
|
||||
user has entered valid information to the form at hand.
|
||||
|
||||
Similarly `--failColor` is used to style errors and `--attentionColor` is used to draw user's
|
||||
attention to certain UI components (e.g. required form inputs, or important info in Inbox)
|
||||
|
||||
Our greyscale colors (for borders and background colors) are named with prefix _"matter"_.
|
||||
|
||||
Similar pattern is also used to create more consistent UI components by providing variables for
|
||||
box-shadows, border-radiuses, transitions, and so on. Our current plan is to parameterize styling
|
||||
even more using this concept.
|
||||
|
||||
Breakpoints for media queries are also defined in this file:
|
||||
|
||||
```css
|
||||
/* ================ Custom media queries ================ */
|
||||
|
||||
@custom-media --viewportMedium (min-width: 768px);
|
||||
@custom-media --viewportLarge (min-width: 1024px);
|
||||
@custom-media --viewportXLarge (min-width: 1921px);
|
||||
```
|
||||
|
||||
### marketplaceFonts.css
|
||||
|
||||
Fonts are specified in this files using CSS Property Sets. They provide us a solid way of creating a
|
||||
fixed set of CSS rules for a specific font.
|
||||
|
||||
For example, our default font is defined as:
|
||||
|
||||
```css
|
||||
--fontWeightMedium: 500;
|
||||
|
||||
--marketplaceDefaultFontStyles: {
|
||||
font-family: 'sofiapro', Helvetica, Arial, sans-serif;
|
||||
font-weight: var(--fontWeightMedium);
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
letter-spacing: -0.1px;
|
||||
|
||||
@media (--viewportMedium) {
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
And created property set can be used as:
|
||||
|
||||
```css
|
||||
p {
|
||||
@apply --marketplaceDefaultFontStyles;
|
||||
}
|
||||
```
|
||||
|
||||
_marketplaceFonts.css_ are included to _marketplace.css_, so you don't need to import this file on
|
||||
new components (importing _marketplace.css_ is enough).
|
||||
|
||||
⚠️ NOTE: template app is following a pattern where the height of an element should be divisible by
|
||||
`6px` on mobile layout and `8px` on bigger layouts. This affects line-heights of font styles too.
|
||||
|
||||
⚠️ NOTE: the `@apply` rule and custom property sets most likely won't get any more support from
|
||||
browser vendors as the spec is yet considered deprecated and alternative solutions are being
|
||||
discussed. However, template app will use these until a good enough alternative is available.
|
||||
|
||||
### marketplaceIndex.css
|
||||
|
||||
This file provides default styles for plain elements like `<body>`, `<a>`, `<p>`, `<input>`, `<h1>`,
|
||||
`<h2>`, and so on.
|
||||
|
||||
## Styling components
|
||||
|
||||
Styling a web UI is traditionally quite a messy business due to the global nature of stylesheets and
|
||||
especially their cascading specificity rule. `.card {/*...*/}` will affect every element on a web
|
||||
page that has a class `card` - even if the different UI context would like to use a different set of
|
||||
rules.
|
||||
|
||||
Our goal has been to create independent components that can be reused in the UI without paying too
|
||||
much attention to the global CSS context. To achieve this, we have used
|
||||
[CSS Modules](https://github.com/css-modules/css-modules), which keeps the syntax close to plain
|
||||
CSS, but it actually creates unique class names to remove the problems caused by the global nature
|
||||
of CSS. In practice, this means that a class with name `card` is actually renamed as
|
||||
`ComponentName__card__3kj4h5`.
|
||||
|
||||
To use styles defined in SectionHero.css, we need to import the .css file into the component:
|
||||
|
||||
```jsx
|
||||
import css from './SectionHero.css';
|
||||
```
|
||||
|
||||
and then select the correct class from imported style object (in this case `heroMainTitle`):
|
||||
|
||||
```jsx
|
||||
<h1 className={css.heroMainTitle}>Book saunas everywhere</h1>
|
||||
```
|
||||
|
||||
### Find the component to change its styles
|
||||
|
||||
Quite often one needs to find a component that is responsible for certain UI partial in order to
|
||||
change the styles. In this case, the easiest way to pinpoint a component is to open the inspector
|
||||
from browser's dev tools. (Right-click on top of the correct element, and select _Inspector_, or
|
||||
something of the sort depending on the browser, from the context menu.)
|
||||
|
||||

|
||||
|
||||
Here we have opened title on LandingPage and the styles for
|
||||
`<h1 class="SectionHero__heroMainTitle__3mVNg"><span>Book saunas everywhere.</span></h1>` are
|
||||
defined in a "class" called `SectionHero__heroMainTitle__3mVNg`. As stated before, the first part of
|
||||
a class name is actually giving us a hint about what component is defining that style - in this
|
||||
case, it's _SectionHero_ and its styles can be found from the file:
|
||||
`src/components/SectionHero/SectionHero.css`.
|
||||
|
||||
There's only two groups of components that break that rule:
|
||||
|
||||
* _src/containers_ (These components are connected to Redux store: Pages and TopbarContainer)
|
||||
* _src/forms_
|
||||
|
||||
### Styling guidelines and good practices
|
||||
|
||||
We have a practice of naming the outermost class of a component as `.root { /* styles */ }`. So, if
|
||||
the component is just rendering single element it only has `.root` class, and if there's more
|
||||
complex inner DOM structure needed, additional classes are named semantically.
|
||||
|
||||
`<SectionHero>` could contain classes named as `.root`, `.heroMainTitle`, `.heroSubtitle`.
|
||||
|
||||
Some guidelines we have tried to follow:
|
||||
|
||||
* **Use semantic class names** (They improve readability and decouples style changes from DOM
|
||||
changes.)
|
||||
* **Use CSS Properties defined in marketplace.css** and create new ones when it makes sense.
|
||||
* **Use classes**, don't style DOM elements directly. (Element styles are global even with CSS
|
||||
Modules.)
|
||||
* **Avoid nesting styles**. (CSS Modules makes specificity rules unnecessary.)
|
||||
* **Group and comment style rules** inside declaration block if that improves readability.
|
||||
* **Parent component is responsible for allocating space** for a child component (i.e. dimensions
|
||||
and margins).
|
||||
* **Define `@apply` rules early enough** inside declaration block (since rules inside those property
|
||||
sets might overwrite rules written above the line where the set is applied).
|
||||
* **Align text and components** to horizontal baselines. I.e. they should be a multiple of `6px` on
|
||||
mobile layout and `8px` on bigger screens.
|
||||
* **Component height should follow baselines too**. I.e. they should be a multiple of `6px` on
|
||||
mobile layout and `8px` on bigger screens. _(Unfortunately, we haven't been strict with this
|
||||
one.)_
|
||||
|
||||
### Styling responsibility: parent component and its children
|
||||
|
||||
One important aspect of a component-based UI is the fact that a component is usually only
|
||||
responsible for what happens inside its outermost element boundary. In parent-child context this
|
||||
means that the parent component is responsible for its own layout and therefore it usually needs to
|
||||
be able to give some dimensions to its child components (and naturally margins between them).
|
||||
|
||||
This creates a need for the parent to have means to pass `className` to its child as props. One
|
||||
example could be a component that shows a circle component inside itself and makes it 50px wide.
|
||||
|
||||
Style definitions of the (`<Circle />`) child component:
|
||||
|
||||
```css
|
||||
.root {
|
||||
background-color: tomato;
|
||||
border-radius: 50%;
|
||||
}
|
||||
```
|
||||
|
||||
Parent component renders
|
||||
(`<div className={css.root}><Circle className={css.circleDimensions} /></div>`):
|
||||
|
||||
```css
|
||||
.root {
|
||||
min-width: 60px;
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.circleDimensions {
|
||||
flex-grow: 0;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin: 5px;
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes the child component needs to be styled even further than just allocating space to it. If
|
||||
the parent component wants to change the theme of child component there are generally two concepts
|
||||
available:
|
||||
|
||||
* Create themed components (e.g. `<PrimaryButton>`, `<SecondaryButton>`, `<InlineButton>`)
|
||||
* Pass in a `class` property that is able to overwrite original styling rules.
|
||||
|
||||
For the latter option, we have created a prop type concept called `rootClassName`. If you pass
|
||||
`rootClassName` through props to a component, it will use that instead of component's own style
|
||||
rules defined in `.root`. This ensures that the order of style declarations inside final CSS bundle
|
||||
doesn't affect the final styles. (CSS bundle is generated in an import order, therefore we want to
|
||||
avoid situations where `<Component className="classA classB"/>` could end up overwriting each others
|
||||
depending on the order they are imported.)
|
||||
|
||||
In some complex cases, we have also created props for overwriting some inner classes of child
|
||||
components. In these cases, child component is also replacing its own styling class with the class
|
||||
passed-in through props. For example, `<LocationAutocompleteInput>` can take a prop called
|
||||
`iconClassName`, which (if given) replaces `.icon` class defined inside
|
||||
`LocationAutocompleteInput.css`.
|
||||
https://www.sharetribe.com/docs/guides/how-to-customize-ftw-styles/
|
||||
|
|
|
|||
|
|
@ -1,16 +1,5 @@
|
|||
# Terms of Service and Privacy Policy
|
||||
|
||||
The Terms of Service and Privacy Policy of a marketplace are static components that can be edited to
|
||||
suit the marketplace.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Terms of Service
|
||||
|
||||
Edit
|
||||
[src/components/TermsOfService/TermsOfService.js](../src/components/TermsOfService/TermsOfService.js)
|
||||
file to customize the default dummy content.
|
||||
|
||||
## Privacy Policy
|
||||
|
||||
Edit
|
||||
[src/components/PrivacyPolicy/PrivacyPolicy.js](../src/components/PrivacyPolicy/PrivacyPolicy.js)
|
||||
file to customize the default dummy content.
|
||||
https://www.sharetribe.com/docs/guides/how-to-change-tos-and-privacy-policy-in-ftw/
|
||||
|
|
|
|||
|
|
@ -1,30 +1,5 @@
|
|||
# Testing Flex template for the web
|
||||
# Testing Flex Template for Web
|
||||
|
||||
The test setup is based on the `create-react-app` test setup and is using the
|
||||
[Jest testing framework](https://facebook.github.io/jest/). See the
|
||||
[Testing Components](create-react-app.md#testing-components) and
|
||||
[Experimental Snapshot Testing](create-react-app.md#experimental-snapshot-testing) sections in the
|
||||
`create-react-app` documentation.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
## Running tests
|
||||
|
||||
To start the test watcher that automatically updates when files change, run
|
||||
|
||||
yarn test
|
||||
|
||||
If you want to run the tests once and not start the watcher, run
|
||||
|
||||
CI=true yarn test
|
||||
|
||||
Note that this also runs the linter.
|
||||
|
||||
## Jest
|
||||
|
||||
To learn more about testing with Jest, read the following documentation:
|
||||
|
||||
* [Getting Started](https://facebook.github.io/jest/docs/getting-started.html)
|
||||
* [Tutorial - React](https://facebook.github.io/jest/docs/tutorial-react.html)
|
||||
* [Tutorial - Async](https://facebook.github.io/jest/docs/tutorial-async.html)
|
||||
* [Snapshot Testing](https://facebook.github.io/jest/blog/2016/07/27/jest-14.html) blog post
|
||||
* [API Reference](https://facebook.github.io/jest/docs/api.html) lists the global environment with
|
||||
the available functions and the assertion matchers
|
||||
https://www.sharetribe.com/docs/guides/how-to-test-ftw/
|
||||
|
|
|
|||
|
|
@ -1,144 +1,5 @@
|
|||
# Translations
|
||||
|
||||
The Flex template for web supports having a single language for the UI. By default the language is
|
||||
English.
|
||||
Documentation moved to the Flex Docs site:
|
||||
|
||||
We are using the [React Intl](https://github.com/yahoo/react-intl) library to translate UI texts and
|
||||
to format dates, numbers, and money values.
|
||||
|
||||
## The translation file
|
||||
|
||||
All the text translations can be found in the
|
||||
[src/translations/en.json](../src/translations/en.json) file. The translation data is formatted as
|
||||
one JSON object with all the translations as properties.
|
||||
|
||||
The key - value syntax is as follows:
|
||||
|
||||
```
|
||||
"<component name>.<translation key>": "<translation>"
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
"ManageListingCard.viewListing": "View listing"
|
||||
```
|
||||
|
||||
The keys are namespaced to the corresponding component. This is aligned with the component driven
|
||||
paradigm that the application follows. It might introduce duplication with same translation texts
|
||||
occurring multiple times in the translation file but it also emphasizes how all the components are
|
||||
independent, how a component can be used anywhere and how modifications to a single component do not
|
||||
affect other components.
|
||||
|
||||
## Using the translations
|
||||
|
||||
React Intl provides multiple ways to access the translation data but the most commonly used are the
|
||||
`formatMessage` function and the `FormattedMessage` tag provided by React Intl.
|
||||
|
||||
To use the `formatMessage` function, component needs to be wrapped with the `injectIntl` function
|
||||
which provides a render prop called `intl`. `intl` then provides all the React Intl translation
|
||||
functions, like `formatMessage`:
|
||||
|
||||
```js
|
||||
import { intlShape, injectIntl } from 'react-intl';
|
||||
|
||||
const SomeComponent = props => {
|
||||
const { intl } = props;
|
||||
|
||||
const translation = intl.formatMessage({ id: 'SomeComponent.someKey' });
|
||||
|
||||
// ...
|
||||
};
|
||||
|
||||
SomeComponent.propTypes = {
|
||||
// from injectIntl
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SomeComponent);
|
||||
```
|
||||
|
||||
As for the `FormattedMessage` it just needs to be imported from `react-intl` and it takes the id
|
||||
prop:
|
||||
|
||||
```
|
||||
<FormattedMessage id="SomeCompoennt.someKey" />
|
||||
```
|
||||
|
||||
Other functions and componets can be explored in the
|
||||
[React Intl documentation](https://github.com/yahoo/react-intl/wiki).
|
||||
|
||||
## Formatting
|
||||
|
||||
React Intl uses the [FormatJS](https://formatjs.io/) formatters for shaping the translation texts
|
||||
based on given arguments. Here are a few examples on how to use FormatJS.
|
||||
|
||||
### Arguments
|
||||
|
||||
Pass a named argument to the format function/component. For the following translation:
|
||||
|
||||
```js
|
||||
"EnquiryForm.messageLabel": "Message to {authorDisplayName}",
|
||||
```
|
||||
|
||||
Pass the author data in the `FormattedMessage` component:
|
||||
|
||||
```js
|
||||
<FormattedMessage id="EnquiryForm.messageLabel" values={{ authorDisplayName: 'Jane D' }} />
|
||||
```
|
||||
|
||||
Or the the `formatMessage` function:
|
||||
|
||||
```js
|
||||
intl.formatMessage({ id: 'EnquiryForm.messageLabel' }, { authorDisplayName: 'Jane D' });
|
||||
```
|
||||
|
||||
### Pluralization
|
||||
|
||||
With pluralization a translation can be formatted to adapt to a number argument.
|
||||
|
||||
```js
|
||||
"ManageListingsPage.youHaveListings": "You have {count} {count, plural, one {listing} other {listings}}",
|
||||
```
|
||||
|
||||
This translation takes the `count` argument and uses the `plural`, `one` and `other` keywords to
|
||||
format the last word of the translation to be _listing_ or _listings_ based on the `count`. The
|
||||
pluralized translation can be used with the `FormattedMessage` component:
|
||||
|
||||
```js
|
||||
<FormattedMessage id="ManageListingsPage.youHaveListings" values={{ count: 3 }} />
|
||||
```
|
||||
|
||||
Or with the `formatMessage` function:
|
||||
|
||||
```js
|
||||
intl.formatMessage({ id: 'ManageListingsPage.youHaveListings' }, { count: 1 });
|
||||
```
|
||||
|
||||
More formatting examples can be found from the
|
||||
[FormatJS message syntax documentation](https://formatjs.io/guides/message-syntax/).
|
||||
|
||||
## Texts outside the translation file
|
||||
|
||||
A few components in the template app contain texts that are not included in the `en.json` file,
|
||||
namely `AboutPage`, `PrivacyPolicy`, and `TermsOfService`. The reason behind this is that these
|
||||
components only contain static content that is laid out in more of a document format so the
|
||||
translations for these texts can easily be changed and maintained in the component files themselves.
|
||||
|
||||
More information about adding static content to the application can be found from
|
||||
[the static pages documentation](./static-pages.md).
|
||||
|
||||
## Changing the language
|
||||
|
||||
For changing the language of the template application:
|
||||
|
||||
1. Copy the default [src/translations/en.json](../src/translations/en.json) English translations
|
||||
file into some other file like `es.json`.
|
||||
|
||||
1. Change the messages in the new translations file to the desired language.
|
||||
|
||||
1. In [src/config.js](../src/config.js), change the `locale` variable value to match the new locale
|
||||
(the name of the new translations file, without the extension).
|
||||
|
||||
1. In [src/app.js](../src/app.js), change the translation imports to point to the correct
|
||||
`react-intl` locale and the new translations file you created.
|
||||
https://www.sharetribe.com/docs/guides/how-to-change-ftw-ui-texts-and-translations/
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Mail templates
|
||||
|
||||
This directory contains the default templates for all mails sent by default as result of activity in
|
||||
the marketplace.
|
||||
This directory contains the default templates for transaction emails. Built-in email templates can
|
||||
be edited in Console.
|
||||
|
||||
Before making customizations, copy the templates to another directory (e.g. `mail-templates`). Note
|
||||
that, at present, changes made to the template files in this repository will not take effect
|
||||
|
|
@ -9,15 +9,14 @@ automatically. Marketplace owner needs to notify the Sharetribe team.
|
|||
|
||||
## Template parts
|
||||
|
||||
A template consists of three files:
|
||||
A template consists of two files:
|
||||
|
||||
* `TEMPLATE_NAME-subject.txt` - holds the mail Subject line template
|
||||
* `TEMPLATE_NAME-text.txt` - holds the template for the plain text version of the mail
|
||||
* `TEMPLATE_NAME-html.html` - contains the template for the HTML version of the mail
|
||||
- `TEMPLATE_NAME-subject.txt` - holds the mail Subject line template
|
||||
- `TEMPLATE_NAME-html.html` - contains the template for the HTML version of the mail
|
||||
|
||||
All the parts are mandatory. All emails that are sent from the marketplace contain both the plain
|
||||
text and HTML variants and the recipient's mail client is free to choose which one to visualize and
|
||||
how.
|
||||
Both parts are mandatory. All emails that are sent from the marketplace contain both the HTML and
|
||||
plain text variants and the recipient's mail client is free to choose which one to visualize and
|
||||
how. The text version is automatically generated from the HTML template.
|
||||
|
||||
## Template syntax
|
||||
|
||||
|
|
@ -35,23 +34,17 @@ Example html:
|
|||
<p>If you didn't make this change, please contact us.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>
|
||||
You have received this email notification because you are a member of {{marketplace.name}}. If
|
||||
you no longer wish to receive these emails, please contact {{marketplace.name}} team.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
Example text version:
|
||||
|
||||
```
|
||||
Hello {{recipient.first-name}},
|
||||
|
||||
Your {{marketplace.name}} account's email address was changed.
|
||||
|
||||
If you didn't make this change, please contact us.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
```
|
||||
|
||||
Variables within `{{ }}` are expanded and escaped, so that they are safe to place inside HTML
|
||||
content. As seen above, some variables have nested values, which can be accessed with dot `.`
|
||||
operator.
|
||||
|
|
@ -61,41 +54,6 @@ The template syntax supports conditionals, loops, helpers and other constructs.
|
|||
|
||||
## List of templates
|
||||
|
||||
### Built-in event mails
|
||||
|
||||
#### email-changed
|
||||
|
||||
Sent to a user when their registered email address has been successfully changed.
|
||||
|
||||
#### new-message
|
||||
|
||||
Sent to a user when the other party in a transaction has sent them a new message.
|
||||
|
||||
#### password-changed
|
||||
|
||||
Sent to a user when their account's password was successfully changed.
|
||||
|
||||
#### reset-password
|
||||
|
||||
Sent to a user when a request for resetting their password is received. The mail contains a link
|
||||
with password reset token and instructs the user to complete the password reset.
|
||||
|
||||
#### verify-changed-email-address
|
||||
|
||||
Sent to a user when they have requested to change their registered email address. The mail contains
|
||||
an email verification link and instructs the user how to verify their email address.
|
||||
|
||||
#### verify-email-address
|
||||
|
||||
Sent to a user after they sign up for an account in the marketplace and instructs them to verify
|
||||
their email address.
|
||||
|
||||
#### user-joined
|
||||
|
||||
Sent to a new marketplace user after they verify their email address.
|
||||
|
||||
### Transaction engine mails
|
||||
|
||||
The templates in the list below correspond to mails defined in the default transaction process.
|
||||
Customized transaction processes can have different set of mail templates.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each line-items}}
|
||||
{{#each tx-line-items}}
|
||||
{{#contains include-for "customer"}}
|
||||
<tr>
|
||||
{{#eq "day" code}}
|
||||
{{#eq "line-item/day" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "day" "days"}}</td>
|
||||
<td style="padding-left: 20px;">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
{{#eq "night" code}}
|
||||
{{#eq "line-item/night" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "night" "nights"}}</td>
|
||||
<td style="padding-left: 20px;">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
|
@ -47,5 +47,9 @@
|
|||
</table>
|
||||
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
|
||||
{{~#*inline "format-money"~}}
|
||||
{{money-amount money}} {{money.currency}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{~#*inline "format-date"~}}
|
||||
{{date date format="MMM d, YYYY"}}
|
||||
{{~/inline~}}
|
||||
|
||||
Your booking request was accepted!
|
||||
|
||||
{{#with transaction~}}
|
||||
{{provider.display-name}} has accepted your booking request for {{listing.title}} from {{> format-date date=booking.start}} to {{> format-date date=booking.end}}.
|
||||
|
||||
We have charged {{> format-money money=payin-total}} from your credit card. Here's your receipt.
|
||||
|
||||
Payment
|
||||
{{#each line-items~}}
|
||||
{{~#contains include-for "customer"~}}
|
||||
{{~#eq "day" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "day" "days"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~#eq "night" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "night" "nights"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~/contains~}}
|
||||
{{/each~}}
|
||||
Payment total: {{> format-money money=payin-total}}
|
||||
|
||||
{{~/with}}
|
||||
|
|
@ -12,5 +12,9 @@
|
|||
{{/with}}
|
||||
|
||||
<p><a href="{{marketplace.url}}">Try to book something else instead</a></p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
{{~#*inline "format-date"~}}
|
||||
{{date date format="MMM d, YYYY"}}
|
||||
{{~/inline~}}
|
||||
|
||||
Your booking request has expired.
|
||||
|
||||
{{#with transaction~}}
|
||||
Unfortunately {{provider.display-name}} didn't respond to your booking request for {{listing.title}} from {{> format-date date=booking.start}} to {{> format-date date=booking.end}} on time, so the request has expired. You have not been billed.
|
||||
{{~/with}}
|
||||
|
||||
Try to book something else instead: {{marketplace.url}}
|
||||
|
|
@ -12,5 +12,9 @@
|
|||
{{/with}}
|
||||
|
||||
<p><a href="{{marketplace.url}}">Try to book something else instead</a></p>
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
{{~#*inline "format-date"~}}
|
||||
{{date date format="MMM d, YYYY"}}
|
||||
{{~/inline~}}
|
||||
|
||||
Your booking request was declined.
|
||||
|
||||
{{#with transaction~}}
|
||||
Unfortunately {{provider.display-name}} decided to decline your booking request for {{listing.title}} from {{> format-date date=booking.start}} to {{> format-date date=booking.end}}. You have not been billed.
|
||||
{{~/with}}
|
||||
|
||||
Try to book something else instead: {{marketplace.url}}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>Your <em>{{marketplace.name}}</em> account's email address was changed.</p>
|
||||
|
||||
<p>If you didn't make this change, please contact us.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{marketplace.name}} email address changed
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
Your {{marketplace.name}} account's email address was changed.
|
||||
|
||||
If you didn't make this change, please contact us.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
|
|
@ -23,20 +23,20 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each line-items}}
|
||||
{{#each tx-line-items}}
|
||||
{{#contains include-for "provider"}}
|
||||
<tr>
|
||||
{{#eq "day" code}}
|
||||
{{#eq "line-item/day" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "day" "days"}}</td>
|
||||
<td style="text-align: right; padding-left: 20px">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
||||
{{#eq "night" code}}
|
||||
{{#eq "line-item/night" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "night" "nights"}}</td>
|
||||
<td style="text-align: right; padding-left: 20px">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
||||
{{#eq "provider-commission" code}}
|
||||
{{#eq "line-item/provider-commission" code}}
|
||||
<td>{{marketplace.name}} fee:</td>
|
||||
<td style="text-align: right; padding-left: 20px;">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
|
@ -53,5 +53,9 @@
|
|||
</table>
|
||||
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
|
||||
{{~#*inline "format-money"~}}
|
||||
{{money-amount money}} {{money.currency}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{~#*inline "format-date"~}}
|
||||
{{date date format="MMM d, YYYY"}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{#with transaction~}}
|
||||
You have been paid {{> format-money money=payout-total}}
|
||||
|
||||
We have sent you {{> format-money money=payout-total}} for the booking of {{listing.title}} from {{> format-date date=booking.start}} to {{> format-date date=booking.end}} by {{customer.display-name}}. It might take up to 7 days for the money to reach your bank account.
|
||||
|
||||
Here's the breakdown.
|
||||
|
||||
Payment
|
||||
{{#each line-items~}}
|
||||
{{~#contains include-for "provider"~}}
|
||||
{{~#eq "day" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "day" "days"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~#eq "night" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "night" "nights"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~#eq "provider-commission" code~}}
|
||||
{{marketplace.name}} fee: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~/contains~}}
|
||||
{{/each~}}
|
||||
You earn: {{> format-money money=payout-total}}
|
||||
{{~/with}}
|
||||
|
|
@ -21,20 +21,20 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each line-items}}
|
||||
{{#each tx-line-items}}
|
||||
{{#contains include-for "provider"}}
|
||||
<tr>
|
||||
{{#eq "day" code}}
|
||||
{{#eq "line-item/day" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "day" "days"}}</td>
|
||||
<td style="text-align: right; padding-left: 20px">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
||||
{{#eq "night" code}}
|
||||
{{#eq "line-item/night" code}}
|
||||
<td>{{> format-money money=unit-price}} × {{number quantity}} {{inflect quantity "night" "nights"}}</td>
|
||||
<td style="text-align: right; padding-left: 20px">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
||||
{{#eq "provider-commission" code}}
|
||||
{{#eq "line-item/provider-commission" code}}
|
||||
<td>{{marketplace.name}} fee:</td>
|
||||
<td style="text-align: right; padding-left: 20px;">{{> format-money money=line-total}}</td>
|
||||
{{/eq}}
|
||||
|
|
@ -55,5 +55,9 @@
|
|||
<p><a href="{{marketplace.url}}/sale/{{url-encode id}}/details">Accept or decline the booking</a></p>
|
||||
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
|
||||
{{~#*inline "format-money"~}}
|
||||
{{money-amount money}} {{money.currency}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{~#*inline "format-date"~}}
|
||||
{{date date format="MMM d, YYYY"}}
|
||||
{{~/inline~}}
|
||||
|
||||
{{#with transaction~}}
|
||||
Please respond to a request by {{customer.display-name}}
|
||||
|
||||
Good news! {{customer.display-name}} just requested to book {{listing.title}} from {{> format-date date=booking.start}} to {{> format-date date=booking.end}}. Here's the breakdown.
|
||||
|
||||
Payment
|
||||
{{#each line-items~}}
|
||||
{{~#contains include-for "provider"~}}
|
||||
{{~#eq "day" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "day" "days"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~#eq "night" code~}}
|
||||
{{> format-money money=unit-price}} x {{number quantity}} {{inflect quantity "night" "nights"}}: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~#eq "provider-commission" code~}}
|
||||
{{marketplace.name}} fee: {{> format-money money=line-total}}
|
||||
{{/eq~}}
|
||||
{{~/contains~}}
|
||||
{{/each~}}
|
||||
You earn: {{> format-money money=payout-total}}
|
||||
|
||||
You need to accept the request by {{> format-date date=delayed-transition.run-at}}. Otherwise the request will be expired and you won't get paid. If the booked dates won't work for you, you can also choose to decline the request.
|
||||
|
||||
Accept or decline the booking: {{marketplace.url}}/sale/{{url-encode id}}/details
|
||||
|
||||
{{~/with}}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
<html>
|
||||
<body>
|
||||
{{#with message}}
|
||||
<h1>You have a new message from {{sender.display-name}}</h1>
|
||||
|
||||
<blockquote>"{{content}}"</blockquote>
|
||||
|
||||
{{#eq recipient-role "customer"}}
|
||||
<p><a href="{{marketplace.url}}/order/{{url-encode transaction.id}}/details">Reply to {{sender.display-name}}</a></p>
|
||||
{{/eq}}
|
||||
|
||||
{{#eq recipient-role "provider"}}
|
||||
<p><a href="{{marketplace.url}}/sale/{{url-encode transaction.id}}/details">Reply to {{sender.display-name}}</a></p>
|
||||
{{/eq}}
|
||||
|
||||
{{/with}}
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{message.sender.display-name}} has sent you a new message
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
{{#with message~}}
|
||||
|
||||
You have a new message from {{sender.display-name}}
|
||||
|
||||
"{{content}}"
|
||||
|
||||
{{#eq recipient-role "customer"~}}
|
||||
Reply to {{sender.display-name}}: {{marketplace.url}}/order/{{url-encode transaction.id}}/details
|
||||
{{/eq~}}
|
||||
|
||||
{{#eq recipient-role "provider"~}}
|
||||
Reply to {{sender.display-name}}: {{marketplace.url}}/sale/{{url-encode transaction.id}}/details
|
||||
{{/eq~}}
|
||||
|
||||
{{~/with}}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>Your <em>{{marketplace.name}}</em> account's password was changed.</p>
|
||||
|
||||
<p>If you didn't make this change, please contact us.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
{{marketplace.name}} password changed
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
Your {{marketplace.name}} account's password was changed.
|
||||
|
||||
If you didn't make this change, please contact us.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>You have indicated that you have forgotten your password for <em>{{marketplace.name}}</em>. Click the following link to reset your password:</p>
|
||||
|
||||
<p><a href="{{marketplace.url}}/reset-password?t={{form-encode password-reset.token}}&e={{form-encode password-reset.email-address}}">{{marketplace.url}}/reset-password?t={{form-encode password-reset.token}}&e={{form-encode password-reset.email-address}}</a></p>
|
||||
|
||||
<p>If you don't use this link within 1 hour, it will expire. You can request a new password reset link, if you need to.</p>
|
||||
|
||||
<p>If you didn't request this, please ignore this email. Your password won't be changed until you use the link above to set a new one.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Password reset instructions for {{marketplace.name}}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
You have indicated that you have forgotten your password for {{marketplace.name}}. Click the following link to reset your password:
|
||||
|
||||
{{marketplace.url}}/reset-password?t={{form-encode password-reset.token}}&e={{form-encode password-reset.email-address}}
|
||||
|
||||
If you don't use this link within 1 hour, it will expire. You can request a new password reset link, if you need to.
|
||||
|
||||
If you didn't request this, please ignore this email. Your password won't be changed until you use the link above to set a new one.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
|
|
@ -7,5 +7,8 @@
|
|||
|
||||
<p><a href="{{marketplace.url}}/order/{{url-encode id}}/details">Leave a review</a></p>
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
{{~#with transaction~}}
|
||||
|
||||
How was your experience with {{listing.title}}? Write a review!
|
||||
|
||||
You recently booked {{listing.title}} from {{provider.display-name}}. Please write a review to describe your experience. Reviews are important part of the {{marketplace.name}} community.
|
||||
|
||||
Leave a review: {{marketplace.url}}/order/{{url-encode id}}/details
|
||||
{{~/with~}}
|
||||
|
|
@ -15,5 +15,9 @@
|
|||
<a href="{{marketplace.url}}/u/{{url-encode recipient.id}}">View your profile.</a>
|
||||
</p>
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
{{~#with transaction~}}
|
||||
|
||||
{{other-party.display-name}} wrote you a review. Here's what they wrote.
|
||||
|
||||
{{#each reviews~}}
|
||||
{{#eq recipient.id subject.id}}"{{content}}"{{/eq}}
|
||||
{{~/each}}
|
||||
|
||||
The review has been published on your {{marketplace.name}} profile.
|
||||
|
||||
View your profile: {{marketplace.url}}/u/{{url-encode recipient.id}}
|
||||
|
||||
{{~/with~}}
|
||||
|
|
@ -9,5 +9,9 @@
|
|||
<a href="{{marketplace.url}}/{{#eq recipient-role "customer"}}order{{else}}sale{{/eq}}/{{url-encode id}}/details">Leave a review</a>
|
||||
</p>
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
{{~#with transaction~}}
|
||||
|
||||
Find out what {{other-party.display-name}} wrote!
|
||||
|
||||
{{other-party.display-name}} has left you a new review. To see what they wrote, please leave them a review to describe your experience.
|
||||
|
||||
Leave a review: {{marketplace.url}}/{{#eq recipient-role "customer"}}order{{else}}sale{{/eq}}/{{url-encode id}}/details
|
||||
|
||||
{{~/with~}}
|
||||
|
|
@ -7,5 +7,9 @@
|
|||
|
||||
<p><a href="{{marketplace.url}}/sale/{{url-encode id}}/details">Leave a review</a></p>
|
||||
{{/with}}
|
||||
|
||||
<hr />
|
||||
|
||||
<p>You have received this email notification because you are a member of {{marketplace.name}}. If you no longer wish to receive these emails, please contact {{marketplace.name}} team.</p>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
|
||||
{{~#with transaction~}}
|
||||
|
||||
How was your experience with {{customer.display-name}}? Write a review!
|
||||
|
||||
{{customer.display-name}} recently booked {{listing.title}} from you. Please write {{customer.display-name}} a review to describe your experience. Reviews are important part of the {{marketplace.name}} community.
|
||||
|
||||
Leave a review: {{marketplace.url}}/sale/{{url-encode id}}/details
|
||||
{{~/with~}}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>Welcome to <em>{{marketplace.name}}</em>!</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Welcome to {{marketplace.name}}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
Welcome to {{marketplace.name}}!
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>To complete your email address change in <em>{{marketplace.name}}</em>, verify your email by clicking the link below:</p>
|
||||
|
||||
<p><a href="{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}">{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}</a></p>
|
||||
|
||||
<p>Alternatively, you can copy the link to your browser's address bar.</p>
|
||||
|
||||
<p>If you don't use this link within 2 days, it will expire. You can request a new verification link, if you need to.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Email verification instructions for {{marketplace.name}}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
To complete your email address change in {{marketplace.name}}, verify your email by clicking the link below:
|
||||
|
||||
{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}
|
||||
|
||||
Alternatively, you can copy the link to your browser's address bar.
|
||||
|
||||
If you don't use this link within 2 days, it will expire. You can request a new verification link, if you need to.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
<html>
|
||||
<body>
|
||||
<p>Hello {{recipient.first-name}},</p>
|
||||
|
||||
<p>To complete your signup to <em>{{marketplace.name}}</em>, verify your email by clicking the link below:</p>
|
||||
|
||||
<p><a href="{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}">{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}</a></p>
|
||||
|
||||
<p>Alternatively, you can copy the link to your browser's address bar.</p>
|
||||
|
||||
<p>If you don't use this link within 2 days, it will expire. You can request a new verification link, if you need to.</p>
|
||||
|
||||
<p>Best regards,<br />The {{marketplace.name}} team</p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1 +0,0 @@
|
|||
Email verification instructions for {{marketplace.name}}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
Hello {{recipient.first-name}},
|
||||
|
||||
To complete your signup to {{marketplace.name}}, verify your email by clicking the link below:
|
||||
|
||||
{{marketplace.url}}/verify-email?t={{form-encode email-verification.token}}
|
||||
|
||||
Alternatively, you can copy the link to your browser's address bar.
|
||||
|
||||
If you don't use this link within 2 days, it will expire. You can request a new verification link, if you need to.
|
||||
|
||||
Best regards,
|
||||
The {{marketplace.name}} team
|
||||
129
package.json
|
|
@ -1,90 +1,115 @@
|
|||
{
|
||||
"name": "app",
|
||||
"version": "2.1.0",
|
||||
"version": "3.5.1",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@formatjs/intl-relativetimeformat": "^2.8.2",
|
||||
"@mapbox/polyline": "^1.0.0",
|
||||
"@sentry/browser": "5.6.2",
|
||||
"@sentry/node": "5.6.2",
|
||||
"array-includes": "^3.0.3",
|
||||
"array.prototype.find": "^2.0.4",
|
||||
"autosize": "^4.0.0",
|
||||
"basic-auth": "^2.0.0",
|
||||
"body-parser": "^1.18.2",
|
||||
"classnames": "^2.2.5",
|
||||
"compression": "^1.7.2",
|
||||
"cookie-parser": "^1.4.3",
|
||||
"core-js": "^2.5.0",
|
||||
"decimal.js": "7.2.4",
|
||||
"dotenv": "4.0.0",
|
||||
"dotenv-expand": "4.0.1",
|
||||
"express": "^4.16.3",
|
||||
"basic-auth": "^2.0.1",
|
||||
"body-parser": "^1.18.3",
|
||||
"classnames": "^2.2.6",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"core-js": "^3.1.4",
|
||||
"decimal.js": "10.2.0",
|
||||
"dotenv": "6.2.0",
|
||||
"dotenv-expand": "4.2.0",
|
||||
"express": "^4.16.4",
|
||||
"express-enforces-ssl": "^1.1.0",
|
||||
"express-sitemap": "^1.8.0",
|
||||
"final-form": "^4.4.0",
|
||||
"final-form-arrays": "^1.0.4",
|
||||
"helmet": "^3.12.0",
|
||||
"lodash": "^4.17.5",
|
||||
"final-form": "^4.18.5",
|
||||
"final-form-arrays": "^3.0.0",
|
||||
"full-icu": "^1.3.0",
|
||||
"helmet": "^3.18.0",
|
||||
"intl-pluralrules": "^1.0.3",
|
||||
"lodash": "^4.17.14",
|
||||
"mapbox-gl-multitouch": "^1.0.3",
|
||||
"moment": "^2.20.1",
|
||||
"moment": "^2.22.2",
|
||||
"object.entries": "^1.0.4",
|
||||
"object.values": "^1.0.4",
|
||||
"path-to-regexp": "^2.2.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"path-to-regexp": "^3.0.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^5.1.1",
|
||||
"raf": "3.4.0",
|
||||
"raven": "^2.4.2",
|
||||
"raven-js": "^3.24.0",
|
||||
"react": "^16.3.1",
|
||||
"react-dates": "^16.0.0",
|
||||
"react-dom": "^16.3.1",
|
||||
"react-final-form": "^3.1.5",
|
||||
"react-final-form-arrays": "^1.0.4",
|
||||
"raf": "^3.4.0",
|
||||
"react": "^16.9.0",
|
||||
"react-dates": "^20.3.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-final-form": "^6.3.0",
|
||||
"react-final-form-arrays": "^3.1.1",
|
||||
"react-google-maps": "^9.4.5",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-intl": "^2.4.0",
|
||||
"react-moment-proptypes": "^1.5.0",
|
||||
"react-redux": "^5.0.7",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"redux": "^4.0.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"sanitize.css": "^5.0.0",
|
||||
"seedrandom": "^2.4.3",
|
||||
"sharetribe-flex-sdk": "^1.0.0",
|
||||
"sharetribe-scripts": "1.1.5",
|
||||
"react-helmet-async": "^1.0.2",
|
||||
"react-intl": "^3.1.13",
|
||||
"react-moment-proptypes": "^1.6.0",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-router-dom": "^5.0.1",
|
||||
"redux": "^4.0.1",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"seedrandom": "^3.0.3",
|
||||
"sharetribe-flex-sdk": "^1.5.0",
|
||||
"sharetribe-scripts": "3.1.1",
|
||||
"smoothscroll-polyfill": "^0.4.0",
|
||||
"source-map-support": "^0.5.4",
|
||||
"source-map-support": "^0.5.9",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-adapter-react-16": "^1.1.1",
|
||||
"enzyme-to-json": "^3.3.3",
|
||||
"babel-jest": "24.9.0",
|
||||
"bfj": "^7.0.1",
|
||||
"chalk": "^2.4.1",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.12.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"inquirer": "^7.0.0",
|
||||
"nodemon": "^1.17.2",
|
||||
"nsp": "^3.2.1",
|
||||
"nsp-preprocessor-yarn": "^1.0.1",
|
||||
"prettier": "^1.11.1"
|
||||
"prettier": "^1.18.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"react-dates/lodash": "^4.17.14",
|
||||
"react-google-maps/lodash": "^4.17.14",
|
||||
"react-test-renderer": "^16.9.0"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"execMap": {
|
||||
"js": "node --icu-data-dir=node_modules/full-icu"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"audit": "yarn audit --json | node scripts/audit.js",
|
||||
"clean": "rm -rf build/*",
|
||||
"dev": "sharetribe-scripts start",
|
||||
"config": "node scripts/config.js",
|
||||
"dev": "node scripts/config.js --check && sharetribe-scripts start",
|
||||
"build": "sharetribe-scripts build",
|
||||
"format": "prettier --write '**/*.{js,css}'",
|
||||
"format-ci": "prettier --list-different '**/*.{js,css}'",
|
||||
"format-docs": "prettier --write '**/*.md'",
|
||||
"test": "sharetribe-scripts test --env=jsdom",
|
||||
"test-ci": "sharetribe-scripts test --env=jsdom --runInBand",
|
||||
"test": "NODE_ICU_DATA=node_modules/full-icu sharetribe-scripts test",
|
||||
"test-ci": "sharetribe-scripts test --runInBand",
|
||||
"eject": "sharetribe-scripts eject",
|
||||
"start": "node server/index.js",
|
||||
"start": "node --icu-data-dir=node_modules/full-icu server/index.js",
|
||||
"dev-server": "export NODE_ENV=development PORT=4000 REACT_APP_CANONICAL_ROOT_URL=http://localhost:4000&&yarn run build&&nodemon --watch server server/index.js",
|
||||
"heroku-postbuild": "yarn run build",
|
||||
"audit": "nsp check --preprocessor yarn"
|
||||
"translate": "node scripts/translations.js"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"proseWrap": "always"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8.11"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="root"><!--!body--></div>
|
||||
<div id="portal-root"></div>
|
||||
<script>
|
||||
(function(document){
|
||||
// Check if Promise exists in window
|
||||
|
|
@ -149,8 +150,8 @@
|
|||
We didn't bundle it to the main app since it would unnecessarily slow down initial rendering.
|
||||
-->
|
||||
<script src="%PUBLIC_URL%/static/scripts/mapbox/mapbox-sdk.min.js"></script>
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v0.47.0/mapbox-gl.css" rel="stylesheet" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v0.47.0/mapbox-gl.js"></script>
|
||||
<link href="https://api.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.css" rel="stylesheet" />
|
||||
<script src="https://api.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.js"></script>
|
||||
<script>
|
||||
window.mapboxgl.accessToken = '%REACT_APP_MAPBOX_ACCESS_TOKEN%';
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -28,67 +28,12 @@
|
|||
color: #fff;
|
||||
background: #00a699
|
||||
}
|
||||
.SingleDatePicker {
|
||||
position: relative;
|
||||
display: inline-block
|
||||
}
|
||||
.SingleDatePicker__block {
|
||||
display: block
|
||||
}
|
||||
.SingleDatePicker_picker {
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
position: absolute
|
||||
}
|
||||
.SingleDatePicker_picker__rtl {
|
||||
direction: rtl
|
||||
}
|
||||
.SingleDatePicker_picker__directionLeft {
|
||||
left: 0
|
||||
}
|
||||
.SingleDatePicker_picker__directionRight {
|
||||
right: 0
|
||||
}
|
||||
.SingleDatePicker_picker__portal {
|
||||
background-color: rgba(0,0,0,.3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
.SingleDatePicker_picker__fullScreenPortal {
|
||||
background-color: #fff
|
||||
}
|
||||
.SingleDatePicker_closeButton {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 15px;
|
||||
z-index: 2
|
||||
}
|
||||
.SingleDatePicker_closeButton:focus,
|
||||
.SingleDatePicker_closeButton:hover {
|
||||
color: darken(#cacccd,10%);
|
||||
text-decoration: none
|
||||
}
|
||||
.SingleDatePicker_closeButton_svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
fill: #cacccd
|
||||
}
|
||||
.SingleDatePickerInput {
|
||||
display: inline-block;
|
||||
background-color: #fff
|
||||
}
|
||||
.SingleDatePickerInput__withBorder {
|
||||
border-radius: 2px;
|
||||
border: 1px solid #dbdbdb
|
||||
}
|
||||
.SingleDatePickerInput__rtl {
|
||||
|
|
@ -159,28 +104,28 @@
|
|||
width: 14px;
|
||||
vertical-align: middle
|
||||
}
|
||||
.DateRangePicker {
|
||||
.SingleDatePicker {
|
||||
position: relative;
|
||||
display: inline-block
|
||||
}
|
||||
.DateRangePicker__block {
|
||||
.SingleDatePicker__block {
|
||||
display: block
|
||||
}
|
||||
.DateRangePicker_picker {
|
||||
.SingleDatePicker_picker {
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
position: absolute
|
||||
}
|
||||
.DateRangePicker_picker__rtl {
|
||||
.SingleDatePicker_picker__rtl {
|
||||
direction: rtl
|
||||
}
|
||||
.DateRangePicker_picker__directionLeft {
|
||||
.SingleDatePicker_picker__directionLeft {
|
||||
left: 0
|
||||
}
|
||||
.DateRangePicker_picker__directionRight {
|
||||
.SingleDatePicker_picker__directionRight {
|
||||
right: 0
|
||||
}
|
||||
.DateRangePicker_picker__portal {
|
||||
.SingleDatePicker_picker__portal {
|
||||
background-color: rgba(0,0,0,.3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
|
@ -188,10 +133,10 @@
|
|||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
.DateRangePicker_picker__fullScreenPortal {
|
||||
.SingleDatePicker_picker__fullScreenPortal {
|
||||
background-color: #fff
|
||||
}
|
||||
.DateRangePicker_closeButton {
|
||||
.SingleDatePicker_closeButton {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
color: inherit;
|
||||
|
|
@ -205,107 +150,16 @@
|
|||
padding: 15px;
|
||||
z-index: 2
|
||||
}
|
||||
.DateRangePicker_closeButton:focus,
|
||||
.DateRangePicker_closeButton:hover {
|
||||
.SingleDatePicker_closeButton:focus,
|
||||
.SingleDatePicker_closeButton:hover {
|
||||
color: darken(#cacccd,10%);
|
||||
text-decoration: none
|
||||
}
|
||||
.DateRangePicker_closeButton_svg {
|
||||
.SingleDatePicker_closeButton_svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
fill: #cacccd
|
||||
}
|
||||
.DayPicker {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
text-align: left
|
||||
}
|
||||
.DayPicker__horizontal {
|
||||
background: #fff
|
||||
}
|
||||
.DayPicker__verticalScrollable {
|
||||
height: 100%
|
||||
}
|
||||
.DayPicker__hidden {
|
||||
visibility: hidden
|
||||
}
|
||||
.DayPicker__withBorder {
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,.05),0 0 0 1px rgba(0,0,0,.07);
|
||||
border-radius: 3px
|
||||
}
|
||||
.DayPicker_portal__horizontal {
|
||||
box-shadow: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%
|
||||
}
|
||||
.DayPicker_portal__vertical {
|
||||
position: initial
|
||||
}
|
||||
.DayPicker_focusRegion {
|
||||
outline: 0
|
||||
}
|
||||
.DayPicker_weekHeaders {
|
||||
position: relative
|
||||
}
|
||||
.DayPicker_weekHeaders__horizontal {
|
||||
margin-left: 9px
|
||||
}
|
||||
.DayPicker_weekHeader {
|
||||
color: #757575;
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
z-index: 2;
|
||||
padding: 0 13px;
|
||||
text-align: left
|
||||
}
|
||||
.DayPicker_weekHeader__vertical {
|
||||
left: 50%
|
||||
}
|
||||
.DayPicker_weekHeader__verticalScrollable {
|
||||
top: 0;
|
||||
display: table-row;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
background: #fff;
|
||||
margin-left: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center
|
||||
}
|
||||
.DayPicker_weekHeader_ul {
|
||||
list-style: none;
|
||||
margin: 1px 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
font-size: 14px
|
||||
}
|
||||
.DayPicker_weekHeader_li {
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
.DayPicker_transitionContainer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 3px
|
||||
}
|
||||
.DayPicker_transitionContainer__horizontal {
|
||||
-webkit-transition: height .2s ease-in-out;
|
||||
-moz-transition: height .2s ease-in-out;
|
||||
transition: height .2s ease-in-out
|
||||
}
|
||||
.DayPicker_transitionContainer__vertical {
|
||||
width: 100%
|
||||
}
|
||||
.DayPicker_transitionContainer__verticalScrollable {
|
||||
padding-top: 20px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
overflow-y: scroll
|
||||
}
|
||||
.DayPickerKeyboardShortcuts_buttonReset {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
|
|
@ -411,164 +265,6 @@
|
|||
.DayPickerKeyboardShortcuts_closeSvg:hover {
|
||||
fill: #82888a
|
||||
}
|
||||
.KeyboardShortcutRow {
|
||||
list-style: none;
|
||||
margin: 6px 0
|
||||
}
|
||||
.KeyboardShortcutRow__block {
|
||||
margin-bottom: 16px
|
||||
}
|
||||
.KeyboardShortcutRow_keyContainer {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
margin-right: 6px
|
||||
}
|
||||
.KeyboardShortcutRow_keyContainer__block {
|
||||
text-align: left;
|
||||
display: inline
|
||||
}
|
||||
.KeyboardShortcutRow_key {
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
background: #f2f2f2;
|
||||
padding: 2px 6px
|
||||
}
|
||||
.KeyboardShortcutRow_action {
|
||||
display: inline;
|
||||
word-break: break-word;
|
||||
margin-left: 8px
|
||||
}
|
||||
.DayPickerNavigation_container {
|
||||
position: relative;
|
||||
z-index: 2
|
||||
}
|
||||
.DayPickerNavigation_container__vertical {
|
||||
background: #fff;
|
||||
box-shadow: 0 0 5px 2px rgba(0,0,0,.1);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 52px;
|
||||
width: 100%
|
||||
}
|
||||
.DayPickerNavigation_container__verticalScrollable {
|
||||
position: relative
|
||||
}
|
||||
.DayPickerNavigation_button {
|
||||
cursor: pointer;
|
||||
line-height: .78;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
.DayPickerNavigation_button__default {
|
||||
border: 1px solid #e4e7e7;
|
||||
background-color: #fff;
|
||||
color: #757575
|
||||
}
|
||||
.DayPickerNavigation_button__default:focus,
|
||||
.DayPickerNavigation_button__default:hover {
|
||||
border: 1px solid #c4c4c4
|
||||
}
|
||||
.DayPickerNavigation_button__default:active {
|
||||
background: #f2f2f2
|
||||
}
|
||||
.DayPickerNavigation_button__horizontal {
|
||||
border-radius: 3px;
|
||||
padding: 6px 9px;
|
||||
top: 18px;
|
||||
position: absolute
|
||||
}
|
||||
.DayPickerNavigation_leftButton__horizontal {
|
||||
left: 22px
|
||||
}
|
||||
.DayPickerNavigation_rightButton__horizontal {
|
||||
right: 22px
|
||||
}
|
||||
.DayPickerNavigation_button__vertical {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 50%
|
||||
}
|
||||
.DayPickerNavigation_button__vertical__default {
|
||||
padding: 5px
|
||||
}
|
||||
.DayPickerNavigation_nextButton__vertical__default {
|
||||
border-left: 0
|
||||
}
|
||||
.DayPickerNavigation_nextButton__verticalScrollable {
|
||||
width: 100%
|
||||
}
|
||||
.DayPickerNavigation_svg__horizontal {
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
fill: #82888a
|
||||
}
|
||||
.DayPickerNavigation_svg__vertical {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
fill: #565a5c
|
||||
}
|
||||
.CalendarMonthGrid {
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
z-index: 0
|
||||
}
|
||||
.CalendarMonthGrid__animating {
|
||||
z-index: 1
|
||||
}
|
||||
.CalendarMonthGrid__horizontal {
|
||||
position: absolute;
|
||||
left: 9px
|
||||
}
|
||||
.CalendarMonthGrid__vertical {
|
||||
margin: 0 auto
|
||||
}
|
||||
.CalendarMonthGrid__vertical_scrollable {
|
||||
margin: 0 auto;
|
||||
overflow-y: scroll
|
||||
}
|
||||
.CalendarMonthGrid_month__horizontal {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
min-height: 100%
|
||||
}
|
||||
.CalendarMonthGrid_month__hideForAnimation {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
pointer-events: none
|
||||
}
|
||||
.CalendarMonth {
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
padding: 0 13px;
|
||||
vertical-align: top;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
.CalendarMonth_table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0
|
||||
}
|
||||
.CalendarMonth_caption {
|
||||
color: #565a5c;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
padding-top: 22px;
|
||||
padding-bottom: 37px;
|
||||
caption-side: initial
|
||||
}
|
||||
.CalendarMonth_caption__verticalScrollable {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 7px
|
||||
}
|
||||
.CalendarDay {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -584,7 +280,7 @@
|
|||
}
|
||||
.CalendarDay__default {
|
||||
border: 1px solid #e4e7e7;
|
||||
color: #565a5c;
|
||||
color: #484848;
|
||||
background: #fff
|
||||
}
|
||||
.CalendarDay__default:hover {
|
||||
|
|
@ -592,10 +288,18 @@
|
|||
border: 1px double #e4e7e7;
|
||||
color: inherit
|
||||
}
|
||||
.CalendarDay__hovered_offset {
|
||||
background: #f4f5f5;
|
||||
border: 1px double #e4e7e7;
|
||||
color: inherit
|
||||
}
|
||||
.CalendarDay__outside {
|
||||
border: 0;
|
||||
background: #fff;
|
||||
color: #565a5c
|
||||
color: #484848
|
||||
}
|
||||
.CalendarDay__outside:hover {
|
||||
border: 0
|
||||
}
|
||||
.CalendarDay__blocked_minimum_nights {
|
||||
background: #fff;
|
||||
|
|
@ -609,12 +313,12 @@
|
|||
}
|
||||
.CalendarDay__highlighted_calendar {
|
||||
background: #ffe8bc;
|
||||
color: #565a5c
|
||||
color: #484848
|
||||
}
|
||||
.CalendarDay__highlighted_calendar:active,
|
||||
.CalendarDay__highlighted_calendar:hover {
|
||||
background: #ffce71;
|
||||
color: #565a5c
|
||||
color: #484848
|
||||
}
|
||||
.CalendarDay__selected_span {
|
||||
background: #66e2da;
|
||||
|
|
@ -662,6 +366,327 @@
|
|||
border: 1px solid #e4e7e7;
|
||||
color: #cacccd
|
||||
}
|
||||
.CalendarMonth {
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
.CalendarMonth_table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0
|
||||
}
|
||||
.CalendarMonth_verticalSpacing {
|
||||
border-collapse: separate
|
||||
}
|
||||
.CalendarMonth_caption {
|
||||
color: #484848;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
padding-top: 22px;
|
||||
padding-bottom: 37px;
|
||||
caption-side: initial
|
||||
}
|
||||
.CalendarMonth_caption__verticalScrollable {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 7px
|
||||
}
|
||||
.CalendarMonthGrid {
|
||||
background: #fff;
|
||||
text-align: left;
|
||||
z-index: 0
|
||||
}
|
||||
.CalendarMonthGrid__animating {
|
||||
z-index: 1
|
||||
}
|
||||
.CalendarMonthGrid__horizontal {
|
||||
position: absolute;
|
||||
left: 9px
|
||||
}
|
||||
.CalendarMonthGrid__vertical {
|
||||
margin: 0 auto
|
||||
}
|
||||
.CalendarMonthGrid__vertical_scrollable {
|
||||
margin: 0 auto;
|
||||
overflow-y: scroll
|
||||
}
|
||||
.CalendarMonthGrid_month__horizontal {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
min-height: 100%
|
||||
}
|
||||
.CalendarMonthGrid_month__hideForAnimation {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
pointer-events: none
|
||||
}
|
||||
.CalendarMonthGrid_month__hidden {
|
||||
visibility: hidden
|
||||
}
|
||||
.DayPickerNavigation {
|
||||
position: relative;
|
||||
z-index: 2
|
||||
}
|
||||
.DayPickerNavigation__horizontal {
|
||||
height: 0
|
||||
}
|
||||
.DayPickerNavigation__verticalDefault {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 52px;
|
||||
bottom: 0;
|
||||
left: 0
|
||||
}
|
||||
.DayPickerNavigation__verticalScrollableDefault {
|
||||
position: relative
|
||||
}
|
||||
.DayPickerNavigation_button {
|
||||
cursor: pointer;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0
|
||||
}
|
||||
.DayPickerNavigation_button__default {
|
||||
border: 1px solid #e4e7e7;
|
||||
background-color: #fff;
|
||||
color: #757575
|
||||
}
|
||||
.DayPickerNavigation_button__default:focus,
|
||||
.DayPickerNavigation_button__default:hover {
|
||||
border: 1px solid #c4c4c4
|
||||
}
|
||||
.DayPickerNavigation_button__default:active {
|
||||
background: #f2f2f2
|
||||
}
|
||||
.DayPickerNavigation_button__horizontalDefault {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
line-height: .78;
|
||||
border-radius: 3px;
|
||||
padding: 6px 9px
|
||||
}
|
||||
.DayPickerNavigation_leftButton__horizontalDefault {
|
||||
left: 22px
|
||||
}
|
||||
.DayPickerNavigation_rightButton__horizontalDefault {
|
||||
right: 22px
|
||||
}
|
||||
.DayPickerNavigation_button__verticalDefault {
|
||||
padding: 5px;
|
||||
background: #fff;
|
||||
box-shadow: 0 0 5px 2px rgba(0,0,0,.1);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
height: 100%;
|
||||
width: 50%
|
||||
}
|
||||
.DayPickerNavigation_nextButton__verticalDefault {
|
||||
border-left: 0
|
||||
}
|
||||
.DayPickerNavigation_nextButton__verticalScrollableDefault {
|
||||
width: 100%
|
||||
}
|
||||
.DayPickerNavigation_svg__horizontal {
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
fill: #82888a;
|
||||
display: block
|
||||
}
|
||||
.DayPickerNavigation_svg__vertical {
|
||||
height: 42px;
|
||||
width: 42px;
|
||||
fill: #484848;
|
||||
display: block
|
||||
}
|
||||
.DayPicker {
|
||||
background: #fff;
|
||||
position: relative;
|
||||
text-align: left
|
||||
}
|
||||
.DayPicker__horizontal {
|
||||
background: #fff
|
||||
}
|
||||
.DayPicker__verticalScrollable {
|
||||
height: 100%
|
||||
}
|
||||
.DayPicker__hidden {
|
||||
visibility: hidden
|
||||
}
|
||||
.DayPicker__withBorder {
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,.05),0 0 0 1px rgba(0,0,0,.07);
|
||||
border-radius: 3px
|
||||
}
|
||||
.DayPicker_portal__horizontal {
|
||||
box-shadow: none;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%
|
||||
}
|
||||
.DayPicker_portal__vertical {
|
||||
position: initial
|
||||
}
|
||||
.DayPicker_focusRegion {
|
||||
outline: 0
|
||||
}
|
||||
.DayPicker_calendarInfo__horizontal,
|
||||
.DayPicker_wrapper__horizontal {
|
||||
display: inline-block;
|
||||
vertical-align: top
|
||||
}
|
||||
.DayPicker_weekHeaders {
|
||||
position: relative
|
||||
}
|
||||
.DayPicker_weekHeaders__horizontal {
|
||||
margin-left: 9px
|
||||
}
|
||||
.DayPicker_weekHeader {
|
||||
color: #757575;
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
z-index: 2;
|
||||
text-align: left
|
||||
}
|
||||
.DayPicker_weekHeader__vertical {
|
||||
left: 50%
|
||||
}
|
||||
.DayPicker_weekHeader__verticalScrollable {
|
||||
top: 0;
|
||||
display: table-row;
|
||||
border-bottom: 1px solid #dbdbdb;
|
||||
background: #fff;
|
||||
margin-left: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: center
|
||||
}
|
||||
.DayPicker_weekHeader_ul {
|
||||
list-style: none;
|
||||
margin: 1px 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
font-size: 14px
|
||||
}
|
||||
.DayPicker_weekHeader_li {
|
||||
display: inline-block;
|
||||
text-align: center
|
||||
}
|
||||
.DayPicker_transitionContainer {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 3px
|
||||
}
|
||||
.DayPicker_transitionContainer__horizontal {
|
||||
-webkit-transition: height .2s ease-in-out;
|
||||
-moz-transition: height .2s ease-in-out;
|
||||
transition: height .2s ease-in-out
|
||||
}
|
||||
.DayPicker_transitionContainer__vertical {
|
||||
width: 100%
|
||||
}
|
||||
.DayPicker_transitionContainer__verticalScrollable {
|
||||
padding-top: 20px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
overflow-y: scroll
|
||||
}
|
||||
.DateInput {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 130px;
|
||||
vertical-align: middle
|
||||
}
|
||||
.DateInput__small {
|
||||
width: 97px
|
||||
}
|
||||
.DateInput__block {
|
||||
width: 100%
|
||||
}
|
||||
.DateInput__disabled {
|
||||
background: #f2f2f2;
|
||||
color: #dbdbdb
|
||||
}
|
||||
.DateInput_input {
|
||||
font-weight: 200;
|
||||
font-size: 19px;
|
||||
line-height: 24px;
|
||||
color: #484848;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
padding: 11px 11px 9px;
|
||||
border: 0;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-left: 0;
|
||||
border-radius: 0
|
||||
}
|
||||
.DateInput_input__small {
|
||||
font-size: 15px;
|
||||
line-height: 18px;
|
||||
letter-spacing: .2px;
|
||||
padding: 7px 7px 5px
|
||||
}
|
||||
.DateInput_input__regular {
|
||||
font-weight: auto
|
||||
}
|
||||
.DateInput_input__readOnly {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
.DateInput_input__focused {
|
||||
outline: 0;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 2px solid #008489;
|
||||
border-left: 0
|
||||
}
|
||||
.DateInput_input__disabled {
|
||||
background: #f2f2f2;
|
||||
font-style: italic
|
||||
}
|
||||
.DateInput_screenReaderMessage {
|
||||
border: 0;
|
||||
clip: rect(0,0,0,0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
width: 1px
|
||||
}
|
||||
.DateInput_fang {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
left: 22px;
|
||||
z-index: 2
|
||||
}
|
||||
.DateInput_fangShape {
|
||||
fill: #fff
|
||||
}
|
||||
.DateInput_fangStroke {
|
||||
stroke: #dbdbdb;
|
||||
fill: transparent
|
||||
}
|
||||
.DateRangePickerInput {
|
||||
background-color: #fff;
|
||||
display: inline-block
|
||||
|
|
@ -670,7 +695,8 @@
|
|||
background: #f2f2f2
|
||||
}
|
||||
.DateRangePickerInput__withBorder {
|
||||
border: 1px solid #cacccd
|
||||
border-radius: 2px;
|
||||
border: 1px solid #dbdbdb
|
||||
}
|
||||
.DateRangePickerInput__rtl {
|
||||
direction: rtl
|
||||
|
|
@ -683,18 +709,15 @@
|
|||
}
|
||||
.DateRangePickerInput_arrow {
|
||||
display: inline-block;
|
||||
vertical-align: middle
|
||||
vertical-align: middle;
|
||||
color: #484848
|
||||
}
|
||||
.DateRangePickerInput_arrow_svg {
|
||||
vertical-align: middle;
|
||||
fill: #565a5c;
|
||||
fill: #484848;
|
||||
height: 24px;
|
||||
width: 24px
|
||||
}
|
||||
.DateRangePickerInput_arrow_svg__small {
|
||||
height: 19px;
|
||||
width: 19px
|
||||
}
|
||||
.DateRangePickerInput_clearDates {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
|
|
@ -751,81 +774,59 @@
|
|||
width: 14px;
|
||||
vertical-align: middle
|
||||
}
|
||||
.DateInput {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
.DateRangePicker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 130px;
|
||||
vertical-align: middle
|
||||
display: inline-block
|
||||
}
|
||||
.DateInput__small {
|
||||
width: 90px
|
||||
.DateRangePicker__block {
|
||||
display: block
|
||||
}
|
||||
.DateInput__disabled {
|
||||
background: #f2f2f2;
|
||||
color: #dbdbdb
|
||||
}
|
||||
.DateInput_input {
|
||||
font-weight: 200;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
color: #565a5c;
|
||||
.DateRangePicker_picker {
|
||||
z-index: 1;
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
padding: 13px 12px 11px;
|
||||
position: absolute
|
||||
}
|
||||
.DateRangePicker_picker__rtl {
|
||||
direction: rtl
|
||||
}
|
||||
.DateRangePicker_picker__directionLeft {
|
||||
left: 0
|
||||
}
|
||||
.DateRangePicker_picker__directionRight {
|
||||
right: 0
|
||||
}
|
||||
.DateRangePicker_picker__portal {
|
||||
background-color: rgba(0,0,0,.3);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%
|
||||
}
|
||||
.DateRangePicker_picker__fullScreenPortal {
|
||||
background-color: #fff
|
||||
}
|
||||
.DateRangePicker_closeButton {
|
||||
background: 0 0;
|
||||
border: 0;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 2px solid transparent;
|
||||
border-left: 0
|
||||
}
|
||||
.DateInput_input__small {
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding: 8px 8px 6px
|
||||
}
|
||||
.DateInput_input__readOnly {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none
|
||||
}
|
||||
.DateInput_input__focused {
|
||||
outline: 0;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 2px solid #008489;
|
||||
border-left: 0
|
||||
}
|
||||
.DateInput_input__disabled {
|
||||
background: #f2f2f2;
|
||||
font-style: italic
|
||||
}
|
||||
.DateInput_screenReaderMessage {
|
||||
border: 0;
|
||||
clip: rect(0,0,0,0);
|
||||
height: 1px;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
line-height: normal;
|
||||
overflow: visible;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
width: 1px
|
||||
}
|
||||
.DateInput_fang {
|
||||
position: absolute;
|
||||
width: 20px;
|
||||
height: 10px;
|
||||
left: 22px;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 15px;
|
||||
z-index: 2
|
||||
}
|
||||
.DateInput_fangShape {
|
||||
fill: #fff
|
||||
}
|
||||
.DateInput_fangStroke {
|
||||
stroke: #dbdbdb;
|
||||
fill: transparent
|
||||
.DateRangePicker_closeButton:focus,
|
||||
.DateRangePicker_closeButton:hover {
|
||||
color: darken(#cacccd,10%);
|
||||
text-decoration: none
|
||||
}
|
||||
.DateRangePicker_closeButton_svg {
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
fill: #cacccd
|
||||
}
|
||||
79
scripts/audit.js
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
// Check possible npm dependency vulnerabilities
|
||||
// Usage: execute "yarn run audit" in the shell
|
||||
//
|
||||
// You can add exceptions through .nsprc file, which is a hidden file in root folder.
|
||||
//
|
||||
// Note: to use this script, you should pipe in the output of 'yarn audit --json'
|
||||
|
||||
const bfj = require('bfj');
|
||||
const fs = require('fs');
|
||||
const exceptions = require('../.auditrc').exceptions;
|
||||
|
||||
const INDENT = ' ';
|
||||
const isAuditAdvisory = o => o.type === 'auditAdvisory';
|
||||
|
||||
// get an advisory or empty object
|
||||
const getAdvisory = o => (!o ? {} : !o.data ? {} : !o.data.advisory ? {} : o.data.advisory);
|
||||
// get a resolution or empty object
|
||||
const getResolution = o => (!o ? {} : !o.data ? {} : !o.data.resolution ? {} : o.data.resolution);
|
||||
|
||||
// Read the output of 'yarn audit --json', which should be piped in through stdin
|
||||
const stdinStream = process.stdin.resume();
|
||||
let advisories = {};
|
||||
bfj
|
||||
.match(stdinStream, (key, value, depth) => depth === 0, { ndjson: true })
|
||||
.on('data', object => {
|
||||
if (isAuditAdvisory(object) && Array.isArray(exceptions)) {
|
||||
const { id, severity, title, url } = getAdvisory(object);
|
||||
const { path } = getResolution(object);
|
||||
const isInExceptionList = url && exceptions.includes(url);
|
||||
|
||||
if (!isInExceptionList && url && path) {
|
||||
const advisory = advisories[id] ? advisories[id] : { url, severity, title };
|
||||
const paths = advisory.paths ? advisory.paths : [];
|
||||
advisories[id] = { ...advisory, paths: paths.concat(path) };
|
||||
}
|
||||
}
|
||||
})
|
||||
.on('dataError', error => {
|
||||
// A syntax error was found in the JSON
|
||||
console.error(
|
||||
`An error occurred while processing data.
|
||||
You need to pipe the results of "yarn audit --json" to this script from shell.
|
||||
Error`,
|
||||
error
|
||||
);
|
||||
process.exit(1);
|
||||
})
|
||||
.on('error', error => {
|
||||
// Some kind of operational error occurred
|
||||
console.error(
|
||||
`An error occurred while processing data.
|
||||
You need to pipe the results of "yarn audit --json" to this script from shell.
|
||||
Error`,
|
||||
error
|
||||
);
|
||||
process.exit(1);
|
||||
})
|
||||
.on('end', error => {
|
||||
const advisoryKeys = Object.keys(advisories);
|
||||
if (advisoryKeys.length > 0) {
|
||||
console.log('Vulneralibilities found:');
|
||||
console.log('\n----------------------------------------\n');
|
||||
|
||||
advisoryKeys.forEach(key => {
|
||||
const { title, severity, url, paths } = advisories[key];
|
||||
console.log(key, `(${title})`);
|
||||
console.log('Severity:', severity);
|
||||
console.log('More info:', url);
|
||||
console.log('Affected dependencies:');
|
||||
paths.forEach(path => {
|
||||
console.log(INDENT, path);
|
||||
});
|
||||
console.log('\n----------------------------------------\n');
|
||||
});
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit();
|
||||
}
|
||||
});
|
||||
418
scripts/config.js
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
const inquirer = require('inquirer');
|
||||
const fs = require('fs');
|
||||
const readline = require('readline');
|
||||
const chalk = require('chalk');
|
||||
|
||||
/**
|
||||
* If config script is running with flag --check only check if .env file exists.
|
||||
* Show error if .env file doesn't exist.
|
||||
*
|
||||
* When running the script without flag .env file is created.
|
||||
* User can fill in environment variables needed for running the application.
|
||||
*
|
||||
* If .env file already exists user can edit the values configured in the file.
|
||||
* Old values are used as default.
|
||||
*
|
||||
*/
|
||||
|
||||
const run = () => {
|
||||
const hasEnvFile = fs.existsSync(`./.env`);
|
||||
|
||||
if (process.argv[2] && process.argv[2] === '--check') {
|
||||
if (!hasEnvFile) {
|
||||
process.on('exit', code => {
|
||||
console.log(`
|
||||
|
||||
${chalk.bold.red(`You don't have required .env file!`)}
|
||||
|
||||
Some environment variables are required before starting Flex template for web. You can create the .env file and configure the variables by running ${chalk.cyan.bold(
|
||||
'yarn run config'
|
||||
)}
|
||||
|
||||
`);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
} else if (hasEnvFile) {
|
||||
console.log(`
|
||||
${chalk.bold.green('.env file already exists!')}
|
||||
Remember to restart the application after editing the environment variables! You can also edit environment variables by editing the .env file directly in your text editor.
|
||||
`);
|
||||
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'editEnvFile',
|
||||
message: 'Do you want to edit the .env file?',
|
||||
default: false,
|
||||
},
|
||||
])
|
||||
.then(answers => {
|
||||
if (answers.editEnvFile) {
|
||||
const settings = findSavedValues();
|
||||
askQuestions(settings);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(chalk.red(`An error occurred due to: ${err.message}`));
|
||||
});
|
||||
} else {
|
||||
inquirer
|
||||
.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'createEmptyEnv',
|
||||
message: `Do you want to configure required environment variables now?
|
||||
${chalk.dim(
|
||||
`If you don't set up variables now .env file is created based on .env-template file. The application will not work correctly without Flex client ID, Stripe publishable API key and MapBox acces token.
|
||||
We recommend setting up the required variables before starting the application!`
|
||||
)}`,
|
||||
default: true,
|
||||
},
|
||||
])
|
||||
.then(answers => {
|
||||
createEnvFile();
|
||||
if (answers.createEmptyEnv) {
|
||||
askQuestions();
|
||||
} else {
|
||||
showSuccessMessage();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(chalk.red(`An error occurred due to: ${err.message}`));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Questions for mandatory variables.
|
||||
* If some values already exist in the .env file use them as a default.
|
||||
* Otherwise don't use default value in the questions.
|
||||
*
|
||||
* @param {array} settings - array of values used as default if user is editing the .env file
|
||||
*
|
||||
* @returns array of questions passed to Inquirer
|
||||
*
|
||||
*/
|
||||
const mandatoryVariables = settings => {
|
||||
const clientIdDefaultMaybe =
|
||||
settings &&
|
||||
settings.REACT_APP_SHARETRIBE_SDK_CLIENT_ID !== '' &&
|
||||
settings.REACT_APP_SHARETRIBE_SDK_CLIENT_ID !== 'change-me'
|
||||
? { default: settings.REACT_APP_SHARETRIBE_SDK_CLIENT_ID }
|
||||
: {};
|
||||
const stripeDefaultMaybe =
|
||||
settings && settings.REACT_APP_STRIPE_PUBLISHABLE_KEY !== ''
|
||||
? { default: settings.REACT_APP_STRIPE_PUBLISHABLE_KEY }
|
||||
: {};
|
||||
const mapBoxDefaultMaybe =
|
||||
settings && settings.REACT_APP_MAPBOX_ACCESS_TOKEN !== ''
|
||||
? { default: settings.REACT_APP_MAPBOX_ACCESS_TOKEN }
|
||||
: {};
|
||||
const currencyDefault =
|
||||
settings && settings.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY !== ''
|
||||
? settings.REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY
|
||||
: 'USD';
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REACT_APP_SHARETRIBE_SDK_CLIENT_ID',
|
||||
message: `What is your Flex client ID?
|
||||
${chalk.dim(
|
||||
'Client ID is needed for connecting with Flex API. You can find your client ID from Flex Console.'
|
||||
)}
|
||||
`,
|
||||
validate: function(value) {
|
||||
if (value.match(/^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i)) {
|
||||
return true;
|
||||
}
|
||||
return 'Please enter valid Flex Client ID. You can check it form Flex Console!';
|
||||
},
|
||||
...clientIdDefaultMaybe,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REACT_APP_STRIPE_PUBLISHABLE_KEY',
|
||||
message: `What is your Stripe publishable key?
|
||||
${chalk.dim(
|
||||
`Stripe publishable API key is for generating tokens with Stripe API. Use test key (prefix pk_test_) for development. Remember to also add the Stripe secret key to Console before testing the payments.
|
||||
If you don't set the Stripe key, payments won't work in the application.`
|
||||
)}
|
||||
`,
|
||||
validate: function(value) {
|
||||
if (value.match(/^pk_/) || value === '') {
|
||||
return true;
|
||||
}
|
||||
return 'Please enter Stripe publishable key with prefix pk_!';
|
||||
},
|
||||
...stripeDefaultMaybe,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REACT_APP_MAPBOX_ACCESS_TOKEN',
|
||||
message: `What is your Mapbox access token?
|
||||
${chalk.dim(
|
||||
`Mapbox is the default map provider of the application. Sign up for Mapbox and go the to account page. Then click Create access token. For more information see the: Integrating to map providers documentation.
|
||||
If you don't set the Mapbox key, the map components won't work in the application.`
|
||||
)}
|
||||
`,
|
||||
...mapBoxDefaultMaybe,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REACT_APP_SHARETRIBE_MARKETPLACE_CURRENCY',
|
||||
message: `What is your marketplace currency?
|
||||
${chalk.dim(
|
||||
'The currency used in the Marketplace must be in ISO 4217 currency code. For example USD, EUR, CAD, AUD, etc. The default value is USD.'
|
||||
)}
|
||||
`,
|
||||
default: function() {
|
||||
return currencyDefault;
|
||||
},
|
||||
validate: function(value) {
|
||||
if (value.match(/^[a-zA-Z]{3}$/)) {
|
||||
return true;
|
||||
}
|
||||
return 'Please enter currency in ISO 4217 format (e.g. USD, EUR, CAD...)';
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Questions for advances variables. User can choose to edit these or not.
|
||||
* If some values already exist in the .env file use them as a default.
|
||||
* Otherwise don't use default value in the questions.
|
||||
*
|
||||
* @param {array} settings - array of values used as default if user is editing the .env file
|
||||
*
|
||||
* @returns array of questions passed to Inquirer
|
||||
*
|
||||
*/
|
||||
|
||||
const advancedSettings = settings => {
|
||||
const rootUrlDefault = settings ? settings.REACT_APP_CANONICAL_ROOT_URL : null;
|
||||
const availabilityDefault = settings
|
||||
? settings.REACT_APP_AVAILABILITY_ENABLED.toLowerCase() === 'true'
|
||||
: true;
|
||||
const searchesDefault = settings
|
||||
? settings.REACT_APP_DEFAULT_SEARCHES_ENABLED.toLowerCase() === 'true'
|
||||
: true;
|
||||
|
||||
return [
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'showAdvancedSettings',
|
||||
message: 'Do you want to edit advanced settings?',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'REACT_APP_CANONICAL_ROOT_URL',
|
||||
message: `What is your canonical root URL?
|
||||
${chalk.dim(
|
||||
'Canonical root URL of the marketplace is needed for social media sharing and SEO optimization. When developing the template application locally URL is usually http://localhost:3000'
|
||||
)}
|
||||
`,
|
||||
default: function() {
|
||||
return rootUrlDefault ? rootUrlDefault : 'http://localhost:3000';
|
||||
},
|
||||
when: function(answers) {
|
||||
return answers.showAdvancedSettings;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'REACT_APP_AVAILABILITY_ENABLED',
|
||||
message: `Do you want to enable availability calendar?
|
||||
${chalk.dim(
|
||||
'This setting enables the Availability Calendar for listings. The default value for this setting is true.'
|
||||
)}
|
||||
`,
|
||||
default: availabilityDefault,
|
||||
when: function(answers) {
|
||||
return answers.showAdvancedSettings;
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'REACT_APP_DEFAULT_SEARCHES_ENABLED',
|
||||
message: `Do you want to enable default search suggestions?
|
||||
${chalk.dim(
|
||||
'This setting enables the Default Search Suggestions in location autocomplete search input. The default value for this setting is true.'
|
||||
)}
|
||||
`,
|
||||
default: searchesDefault,
|
||||
when: function(answers) {
|
||||
return answers.showAdvancedSettings;
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* First ask mandatory variables and then check if user also wants to edit the advanced settings.
|
||||
* Update the .env file with user input.
|
||||
*
|
||||
* @param {array} settings - array of values used as default if user is editing the .env file
|
||||
*
|
||||
*/
|
||||
|
||||
const askQuestions = settings => {
|
||||
inquirer
|
||||
.prompt(mandatoryVariables(settings))
|
||||
.then(answers => {
|
||||
return readLines(answers);
|
||||
})
|
||||
.then(values => {
|
||||
const data = getData(values);
|
||||
updateEnvFile(data);
|
||||
|
||||
console.log(chalk.yellow.bold(`Advanced settings:`));
|
||||
inquirer
|
||||
.prompt(advancedSettings(settings))
|
||||
.then(answers => {
|
||||
return readLines(answers);
|
||||
})
|
||||
.then(values => {
|
||||
const data = getData(values);
|
||||
updateEnvFile(data);
|
||||
showSuccessMessage();
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(chalk.red(`An error occurred due to: ${err.message}`));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Show succes message used after creating the .env file
|
||||
*/
|
||||
const showSuccessMessage = () => {
|
||||
console.log(`
|
||||
${chalk.green.bold('.env file saved succesfully!')}
|
||||
|
||||
Start the Flex template application by running ${chalk.bold.cyan('yarn run dev')}
|
||||
|
||||
Note that the .env file is a hidden file so it might not be visible directly in directory listing. If you want to update the environment variables run ${chalk.cyan.bold(
|
||||
'yarn run config'
|
||||
)} again or edit the .env file directly. Remember to restart the application after editing the environment variables!
|
||||
`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create new .env file using .env-template
|
||||
*/
|
||||
const createEnvFile = () => {
|
||||
fs.copyFileSync('./.env-template', './.env', err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the saved values from .env file so they can be used as a default values
|
||||
*
|
||||
* @return array containing all keys and values saved to .env file
|
||||
*/
|
||||
const findSavedValues = () => {
|
||||
const savedEnvFile = fs.readFileSync('./.env').toString();
|
||||
|
||||
const settings = savedEnvFile.split('\n').reduce((obj, line) => {
|
||||
const splits = line.split('=');
|
||||
const key = splits[0].trim();
|
||||
if (splits.length > 1) {
|
||||
obj[key] = splits
|
||||
.slice(1)
|
||||
.join('=')
|
||||
.trim();
|
||||
}
|
||||
return obj;
|
||||
}, {});
|
||||
|
||||
return settings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Read all lines from existing .env file to array.
|
||||
* If line matches one of the keys in user's answers update add value to that line.
|
||||
* Otherwise keep the original line.
|
||||
*
|
||||
* @param {object} answers object containing all the inputs user has given to questions
|
||||
*
|
||||
* @return returns a Promise that resolves with values object containing user input and read lines
|
||||
*/
|
||||
|
||||
const readLines = answers => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const rl = readline.createInterface({
|
||||
input: fs.createReadStream('./.env'),
|
||||
});
|
||||
|
||||
const lines = [];
|
||||
rl.on('line', function(line) {
|
||||
lines.push(line);
|
||||
});
|
||||
|
||||
rl.on('close', () => {
|
||||
const values = { answers, lines };
|
||||
resolve(values);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates array of data that is saved to .env file
|
||||
*
|
||||
* @param {object} values contais lines read from .env file and answers from the user
|
||||
*
|
||||
* @returns array containing the data that needs to be saved to .env file
|
||||
*/
|
||||
const getData = values => {
|
||||
const { lines, answers } = values;
|
||||
|
||||
const data = lines.map(line => {
|
||||
const key = getKeyFromAnswers(answers, line);
|
||||
if (key) {
|
||||
const value = `${answers[key]}`;
|
||||
return `${key}=${value.trim()}\n`;
|
||||
} else {
|
||||
return `${line}\n`;
|
||||
}
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
/**
|
||||
* Joins lines to create content that is written to .env file. This will overwrite the previous content.
|
||||
*
|
||||
* @param {array} data arry of lines to be written into .env file
|
||||
*/
|
||||
const updateEnvFile = data => {
|
||||
fs.writeFileSync('./.env', data.join(''));
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if line contains the key from the user answers.
|
||||
*
|
||||
* @param {object} answers array of answers user has given to questions
|
||||
* @param {string} line line from the existing .env file
|
||||
*
|
||||
* @returns key if the key matches to the one in the line
|
||||
*/
|
||||
const getKeyFromAnswers = (answers, line) => {
|
||||
let foundKey;
|
||||
if (answers) {
|
||||
foundKey = Object.keys(answers).find(function(key) {
|
||||
if (line.includes(key)) {
|
||||
return key;
|
||||
}
|
||||
});
|
||||
}
|
||||
return foundKey;
|
||||
};
|
||||
|
||||
run();
|
||||
300
scripts/translations.js
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/**
|
||||
* A translations too that can be used to keep a translation file(s)
|
||||
* up to date with one source language file.
|
||||
*
|
||||
* Possible target languages are resolved from the translation files
|
||||
* available. When adding a new tranlation file, the language name can
|
||||
* be added to the TARGET_LANG_NAMES map for clearer prompt messages.
|
||||
* By default the translations are matched agains English translations
|
||||
* but that can be changed by modifying the SOURCE_LANG.
|
||||
*/
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
const difference = require('lodash/difference');
|
||||
const fs = require('fs');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const PATH = './src/translations/';
|
||||
|
||||
const SOURCE_LANG = { name: 'English', code: 'en' };
|
||||
const TARGET_LANG_NAMES = {
|
||||
es: 'Spanish',
|
||||
de: 'German',
|
||||
fr: 'French',
|
||||
};
|
||||
|
||||
class BreakToRun {}
|
||||
class BreakToRunWithTarget {}
|
||||
|
||||
/**
|
||||
* Resolves translation file path for a lang code: en, es, de, etc.
|
||||
*/
|
||||
const filePath = lang => `${PATH}${lang}.json`;
|
||||
|
||||
/**
|
||||
* Resolves name of a target language based on
|
||||
* a language code: en, es, de, etc.
|
||||
*
|
||||
* If a lang name is not found, language code is returned.
|
||||
*/
|
||||
const targetLangName = code => {
|
||||
const name = TARGET_LANG_NAMES[code];
|
||||
|
||||
return name || code;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a list of target language choices by finding all possible
|
||||
* translation files and filtering out the SOURCE_LANG. Provides a list
|
||||
* of objects that can be passed to inquirer.
|
||||
*
|
||||
* Relies on translation file naming: <lang code>.json
|
||||
*/
|
||||
const targetLangChoices = () => {
|
||||
const folder = './src/translations/';
|
||||
const filenames = fs.readdirSync(folder);
|
||||
|
||||
const choices = filenames
|
||||
.filter(name => name.endsWith('.json'))
|
||||
.map(name => name.split('.')[0])
|
||||
.filter(code => code !== SOURCE_LANG.code)
|
||||
.map(code => {
|
||||
return {
|
||||
name: targetLangName(code),
|
||||
value: code,
|
||||
};
|
||||
});
|
||||
|
||||
return choices;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts an object by key.
|
||||
*
|
||||
* Relies on an object keeping the order in which entries are added for string keys.
|
||||
* See: http://exploringjs.com/es6/ch_oop-besides-classes.html#_traversal-order-of-properties
|
||||
*/
|
||||
const sort = obj => {
|
||||
const sorted = {};
|
||||
Object.keys(obj)
|
||||
.sort()
|
||||
.forEach(key => (sorted[key] = obj[key]));
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads a file with JSON content and returns
|
||||
* a JS object.
|
||||
*
|
||||
* @param filepath
|
||||
*/
|
||||
const readFileToJSON = filepath => {
|
||||
const rawdata = fs.readFileSync(filepath);
|
||||
return JSON.parse(rawdata);
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the application.
|
||||
*
|
||||
*/
|
||||
const run = () => {
|
||||
const choices = targetLangChoices();
|
||||
|
||||
selectLanguage(choices)
|
||||
.then(answers => {
|
||||
runWithTarget(answers.lang);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(chalk.red(`An error occurred due to: ${err.message}`));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* When a language is selected this will prompt
|
||||
* the user about adding translations to that language.
|
||||
*
|
||||
* @param {String} targetLang Target language code
|
||||
*/
|
||||
const runWithTarget = targetLang => {
|
||||
let key;
|
||||
let translation;
|
||||
|
||||
const sourceLang = SOURCE_LANG.code;
|
||||
const source = readFileToJSON(filePath(sourceLang));
|
||||
const target = readFileToJSON(filePath(targetLang));
|
||||
const sourceKeys = Object.keys(source);
|
||||
const targetKeys = Object.keys(target);
|
||||
const diff = difference(sourceKeys, targetKeys);
|
||||
|
||||
if (diff.length === 0) {
|
||||
console.log(`No translations missing from the ${targetLangName(targetLang)} translations`);
|
||||
run();
|
||||
} else {
|
||||
translateLanguage(targetLang, source, target, diff);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Does the actual prompting for translations.
|
||||
*
|
||||
* @param {String} targetLang Target language code
|
||||
* @param {Object} source Source translations
|
||||
* @param {Object} target Target translations
|
||||
* @param {Array} diff Missing keys in target
|
||||
*/
|
||||
const translateLanguage = (targetLang, source, target, diff) => {
|
||||
selectKey(targetLang, diff, source, target)
|
||||
.then(answers => {
|
||||
key = answers.key;
|
||||
if (key === null) {
|
||||
throw new BreakToRun();
|
||||
}
|
||||
if (key != null) {
|
||||
return addTranslation(targetLang, key, source);
|
||||
}
|
||||
})
|
||||
.then(answers => {
|
||||
translation = answers.value;
|
||||
return confirmTranslation(targetLang, key, translation);
|
||||
})
|
||||
.then(answers => {
|
||||
// y|Y|n|N
|
||||
const confirmation = answers.value;
|
||||
|
||||
if (/n/i.test(confirmation)) {
|
||||
console.log('Discarding new translation');
|
||||
throw new BreakToRunWithTarget();
|
||||
}
|
||||
|
||||
target[key] = translation;
|
||||
|
||||
const sorted = sort(target);
|
||||
const data = JSON.stringify(sorted, null, 2);
|
||||
|
||||
return updateTranslationsFile(data, targetLang);
|
||||
})
|
||||
.then(() => {
|
||||
runWithTarget(targetLang);
|
||||
})
|
||||
.catch(err => {
|
||||
if (err instanceof BreakToRun) {
|
||||
// break out of Promise chain, start over
|
||||
run();
|
||||
} else if (err instanceof BreakToRunWithTarget) {
|
||||
// break out of Promise chain, continue with this language
|
||||
runWithTarget(targetLang);
|
||||
} else {
|
||||
console.log(chalk.red(`An error occurred due to: ${err.message}`));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt for selecting the language of which
|
||||
* translations are inspected.
|
||||
*
|
||||
* @return a Promise
|
||||
*/
|
||||
const selectLanguage = choices => {
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'lang',
|
||||
message: 'Which language would you like to inspect? (Or hit Ctrl+C to quit)',
|
||||
choices: choices,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt for selecting a key that is missing from the target language translations.
|
||||
*
|
||||
* @param {String} targetLang The language that is being translated
|
||||
* @param {Array} diff Keys missing frrom target language translations
|
||||
*
|
||||
* @return a Promise
|
||||
*/
|
||||
const selectKey = (targetLang, diff, source, target) => {
|
||||
const choices = [{ name: 'Back to languages', value: null }, ...diff];
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'list',
|
||||
name: 'key',
|
||||
message: `The following translation keys (${
|
||||
diff.length
|
||||
}) are missing from the ${targetLangName(
|
||||
targetLang
|
||||
)} translations. Select a key to add a translation.`,
|
||||
choices: choices,
|
||||
pageSize: 30,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt for a new translation in target language.
|
||||
*
|
||||
* @param {String} targetLang The language that is being translated
|
||||
* @param {String} key Key for the new translation
|
||||
* @param {Object} source Source language translations
|
||||
*
|
||||
* @return a Promise
|
||||
*/
|
||||
const addTranslation = (targetLang, key, source) => {
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'value',
|
||||
message: `Please provide a translation in ${targetLangName(
|
||||
targetLang
|
||||
)} for the key ${chalk.blueBright(key)}. The current ${
|
||||
SOURCE_LANG.name
|
||||
} translation is ${chalk.green(source[key])} \n`,
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Prompt for a confirming a new translation with 'y' or 'N'.
|
||||
*
|
||||
* @param {String} targetLang The language that is being translated
|
||||
* @param {String} key Key for the new translation
|
||||
* @param {Object} source Source language translations
|
||||
*
|
||||
* @return a Promise with with answers.value being 'y' or 'N'
|
||||
*/
|
||||
const confirmTranslation = (targetLang, key, translation) => {
|
||||
return inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'value',
|
||||
message: `Do you want to add the ${targetLangName(targetLang)} translation ${chalk.green(
|
||||
translation
|
||||
)} for the key ${chalk.blueBright(key)}? (y/n)`,
|
||||
validate: value => /y/i.test(value) || /n/i.test(value),
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update translations file.
|
||||
*
|
||||
* @param {String} data String data to be written to file
|
||||
* @param {String} targetLang Language code of the translation file
|
||||
*
|
||||
* @return a Promise that resolves when file is written
|
||||
*/
|
||||
const updateTranslationsFile = (data, targetLang) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(filePath(targetLang), data, err => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
console.log(chalk.bold('The translation file is updated'));
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
run();
|
||||
|
|
@ -7,6 +7,7 @@ const unsafeEval = "'unsafe-eval'";
|
|||
const data = 'data:';
|
||||
const blob = 'blob:';
|
||||
const devImagesMaybe = dev ? ['*.localhost:8000'] : [];
|
||||
const baseUrl = process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL || 'https://flex-api.sharetribe.com';
|
||||
|
||||
// Default CSP whitelist.
|
||||
//
|
||||
|
|
@ -18,10 +19,11 @@ const defaultDirectives = {
|
|||
childSrc: [blob],
|
||||
connectSrc: [
|
||||
self,
|
||||
process.env.REACT_APP_SHARETRIBE_SDK_BASE_URL,
|
||||
baseUrl,
|
||||
'maps.googleapis.com',
|
||||
'*.tiles.mapbox.com',
|
||||
'api.mapbox.com',
|
||||
'events.mapbox.com',
|
||||
|
||||
// Google Analytics
|
||||
'www.google-analytics.com',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const path = require('path');
|
|||
const buildPath = path.resolve(__dirname, '..', 'build');
|
||||
const manifestPath = path.join(buildPath, 'asset-manifest.json');
|
||||
const manifest = require(manifestPath);
|
||||
const mainJsPath = path.join(buildPath, manifest['main.js']);
|
||||
const mainJsPath = path.join(buildPath, manifest['files']['main.js']);
|
||||
const mainJs = require(mainJsPath);
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
|
|
@ -167,10 +167,11 @@ app.get('*', (req, res) => {
|
|||
secure: USING_SSL,
|
||||
});
|
||||
|
||||
const baseUrl = BASE_URL ? { baseUrl: BASE_URL } : {};
|
||||
|
||||
const sdk = sharetribeSdk.createInstance({
|
||||
transitVerbose: TRANSIT_VERBOSE,
|
||||
clientId: CLIENT_ID,
|
||||
baseUrl: BASE_URL,
|
||||
httpAgent: httpAgent,
|
||||
httpsAgent: httpsAgent,
|
||||
tokenStore,
|
||||
|
|
@ -182,6 +183,7 @@ app.get('*', (req, res) => {
|
|||
reader: v => new Decimal(v.value),
|
||||
},
|
||||
],
|
||||
...baseUrl,
|
||||
});
|
||||
|
||||
// Until we have a better plan for caching dynamic content and we
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@
|
|||
* or just plain printing errors to the log.
|
||||
*/
|
||||
|
||||
const Raven = require('raven');
|
||||
const Sentry = require('@sentry/node');
|
||||
|
||||
const ENV = process.env.REACT_APP_ENV;
|
||||
const SENTRY_DSN = process.env.SERVER_SENTRY_DSN;
|
||||
const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN;
|
||||
|
||||
/**
|
||||
* Set up error loggin. If a Sentry DSN is defined
|
||||
|
|
@ -19,12 +19,7 @@ exports.setup = () => {
|
|||
// exceptions from starting the server etc. but does not catch the
|
||||
// ones thrown from Express.js middleware functions. For those
|
||||
// an error handler has to be added to the Express app.
|
||||
Raven.config(SENTRY_DSN, {
|
||||
environment: ENV,
|
||||
autoBreadcrumbs: {
|
||||
http: true,
|
||||
},
|
||||
}).install();
|
||||
Sentry.init({ dsn: SENTRY_DSN, environment: ENV });
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -34,7 +29,7 @@ exports.setup = () => {
|
|||
*/
|
||||
exports.requestHandler = () => {
|
||||
if (SENTRY_DSN) {
|
||||
return Raven.requestHandler();
|
||||
return Sentry.Handlers.requestHandler();
|
||||
} else {
|
||||
return (req, res, next) => {
|
||||
next();
|
||||
|
|
@ -48,7 +43,7 @@ exports.requestHandler = () => {
|
|||
*/
|
||||
exports.errorHandler = () => {
|
||||
if (SENTRY_DSN) {
|
||||
return Raven.errorHandler();
|
||||
return Sentry.Handlers.errorHandler();
|
||||
} else {
|
||||
return (err, req, res, next) => {
|
||||
next(err);
|
||||
|
|
@ -56,6 +51,17 @@ exports.errorHandler = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const responseAPIErrors = error => {
|
||||
return error && error.data && error.data.errors ? error.data.errors : [];
|
||||
};
|
||||
|
||||
const responseApiErrorInfo = err =>
|
||||
responseAPIErrors(err).map(e => ({
|
||||
status: e.status,
|
||||
code: e.code,
|
||||
meta: e.meta,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Logs a error. If Sentry client is set up
|
||||
* passes the error to that. Otherwise prints
|
||||
|
|
@ -67,7 +73,15 @@ exports.errorHandler = () => {
|
|||
*/
|
||||
exports.error = (e, code, data) => {
|
||||
if (SENTRY_DSN) {
|
||||
Raven.captureException(e, { tags: { code }, extra: data });
|
||||
const extra = { ...data, apiErrorData: responseApiErrorInfo(e) };
|
||||
|
||||
Sentry.withScope(scope => {
|
||||
scope.setTag('code', code);
|
||||
Object.keys(extra).forEach(key => {
|
||||
scope.setExtra(key, extra[key]);
|
||||
});
|
||||
Sentry.captureException(e);
|
||||
});
|
||||
} else {
|
||||
console.error(e);
|
||||
console.error(code);
|
||||
|
|
|
|||
|
|
@ -78,12 +78,12 @@ class RouteComponentRenderer extends Component {
|
|||
handleLocationChanged(this.props.dispatch, this.props.location);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentDidUpdate() {
|
||||
// Calling loadData after initial rendering (on client side).
|
||||
// This makes it possible to use loadData as default client side data loading technique.
|
||||
// However it is better to fetch data before location change to avoid "Loading data" state.
|
||||
callLoadData(nextProps);
|
||||
handleLocationChanged(nextProps.dispatch, nextProps.location);
|
||||
callLoadData(this.props);
|
||||
handleLocationChanged(this.props.dispatch, this.props.location);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -187,4 +187,7 @@ const mapStateToProps = state => {
|
|||
// lifecycle hook.
|
||||
//
|
||||
// See: https://github.com/ReactTraining/react-router/issues/4671
|
||||
export default compose(withRouter, connect(mapStateToProps))(Routes);
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps)
|
||||
)(Routes);
|
||||
|
|
|
|||
90
src/app.js
|
|
@ -6,23 +6,61 @@ import ReactDOMServer from 'react-dom/server';
|
|||
// https://github.com/airbnb/react-dates#initialize
|
||||
// NOTE: Initializing it here will initialize it also for app.test.js
|
||||
import 'react-dates/initialize';
|
||||
import Helmet from 'react-helmet';
|
||||
import { HelmetProvider } from 'react-helmet-async';
|
||||
import { BrowserRouter, StaticRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import difference from 'lodash/difference';
|
||||
import mapValues from 'lodash/mapValues';
|
||||
import moment from 'moment';
|
||||
import { IntlProvider, addLocaleData } from 'react-intl';
|
||||
import { IntlProvider } from './util/reactIntl';
|
||||
import configureStore from './store';
|
||||
import routeConfiguration from './routeConfiguration';
|
||||
import Routes from './Routes';
|
||||
import config from './config';
|
||||
|
||||
// If you want to change the language, Change the imports to the match
|
||||
// the wanted locale.
|
||||
//
|
||||
// Remember to also change the language in the config.js file.
|
||||
import localeData from 'react-intl/locale-data/en';
|
||||
import messages from './translations/en.json';
|
||||
// Flex template application uses English translations as default.
|
||||
import defaultMessages from './translations/en.json';
|
||||
|
||||
// If you want to change the language, change the imports to match the wanted locale:
|
||||
// 1) Change the language in the config.js file!
|
||||
// 2) Import correct locale rules for Moment library
|
||||
// 3) Use the `messagesInLocale` import to add the correct translation file.
|
||||
// 4) To support older browsers we need add the correct locale for intl-relativetimeformat to `util/polyfills.js`
|
||||
|
||||
// Note that there is also translations in './translations/countryCodes.js' file
|
||||
// This file contains ISO 3166-1 alpha-2 country codes, country names and their translations in our default languages
|
||||
// This used to collect billing address in StripePaymentAddress on CheckoutPage
|
||||
|
||||
// Step 2:
|
||||
// If you are using a non-english locale with moment library,
|
||||
// you should also import time specific formatting rules for that locale
|
||||
// e.g. for French: import 'moment/locale/fr';
|
||||
|
||||
// Step 3:
|
||||
// If you are using a non-english locale, point `messagesInLocale` to correct .json file
|
||||
import messagesInLocale from './translations/fr.json';
|
||||
|
||||
// If translation key is missing from `messagesInLocale` (e.g. fr.json),
|
||||
// corresponding key will be added to messages from `defaultMessages` (en.json)
|
||||
// to prevent missing translation key errors.
|
||||
const addMissingTranslations = (sourceLangTranslations, targetLangTranslations) => {
|
||||
const sourceKeys = Object.keys(sourceLangTranslations);
|
||||
const targetKeys = Object.keys(targetLangTranslations);
|
||||
const missingKeys = difference(sourceKeys, targetKeys);
|
||||
|
||||
const addMissingTranslation = (translations, missingKey) => ({
|
||||
...translations,
|
||||
[missingKey]: sourceLangTranslations[missingKey],
|
||||
});
|
||||
|
||||
return missingKeys.reduce(addMissingTranslation, targetLangTranslations);
|
||||
};
|
||||
|
||||
const isDefaultLanguageInUse = config.locale === 'en';
|
||||
|
||||
const messages = isDefaultLanguageInUse
|
||||
? defaultMessages
|
||||
: addMissingTranslations(defaultMessages, messagesInLocale);
|
||||
|
||||
const isTestEnv = process.env.NODE_ENV === 'test';
|
||||
|
||||
|
|
@ -33,13 +71,12 @@ const localeMessages = isTestEnv ? testMessages : messages;
|
|||
|
||||
const setupLocale = () => {
|
||||
if (isTestEnv) {
|
||||
// Don't change the locale in tests
|
||||
// Use english as a default locale in tests
|
||||
// This affects app.test.js and app.node.test.js tests
|
||||
config.locale = 'en';
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the translation messages
|
||||
addLocaleData([...localeData]);
|
||||
|
||||
// Set the Moment locale globally
|
||||
// See: http://momentjs.com/docs/#/i18n/changing-locale/
|
||||
moment.locale(config.locale);
|
||||
|
|
@ -49,11 +86,13 @@ export const ClientApp = props => {
|
|||
const { store } = props;
|
||||
setupLocale();
|
||||
return (
|
||||
<IntlProvider locale={config.locale} messages={localeMessages}>
|
||||
<IntlProvider locale={config.locale} messages={localeMessages} textComponent="span">
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<Routes routes={routeConfiguration()} />
|
||||
</BrowserRouter>
|
||||
<HelmetProvider>
|
||||
<BrowserRouter>
|
||||
<Routes routes={routeConfiguration()} />
|
||||
</BrowserRouter>
|
||||
</HelmetProvider>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
|
@ -64,14 +103,17 @@ const { any, string } = PropTypes;
|
|||
ClientApp.propTypes = { store: any.isRequired };
|
||||
|
||||
export const ServerApp = props => {
|
||||
const { url, context, store } = props;
|
||||
const { url, context, helmetContext, store } = props;
|
||||
setupLocale();
|
||||
HelmetProvider.canUseDOM = false;
|
||||
return (
|
||||
<IntlProvider locale={config.locale} messages={localeMessages}>
|
||||
<IntlProvider locale={config.locale} messages={localeMessages} textComponent="span">
|
||||
<Provider store={store}>
|
||||
<StaticRouter location={url} context={context}>
|
||||
<Routes routes={routeConfiguration()} />
|
||||
</StaticRouter>
|
||||
<HelmetProvider context={helmetContext}>
|
||||
<StaticRouter location={url} context={context}>
|
||||
<Routes routes={routeConfiguration()} />
|
||||
</StaticRouter>
|
||||
</HelmetProvider>
|
||||
</Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
|
@ -95,9 +137,11 @@ export const renderApp = (url, serverContext, preloadedState) => {
|
|||
// shouldn't do any SDK calls in the (server) rendering lifecycle.
|
||||
const store = configureStore(preloadedState);
|
||||
|
||||
const helmetContext = {};
|
||||
|
||||
const body = ReactDOMServer.renderToString(
|
||||
<ServerApp url={url} context={serverContext} store={store} />
|
||||
<ServerApp url={url} context={serverContext} helmetContext={helmetContext} store={store} />
|
||||
);
|
||||
const head = Helmet.renderStatic();
|
||||
const { helmet: head } = helmetContext;
|
||||
return { head, body };
|
||||
};
|
||||
|
|
|
|||
96
src/app.node.test.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* @jest-environment node
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import forEach from 'lodash/forEach';
|
||||
import { ClientApp, ServerApp } from './app';
|
||||
import configureStore from './store';
|
||||
|
||||
const render = (url, context) => {
|
||||
const store = configureStore();
|
||||
|
||||
const helmetContext = {};
|
||||
|
||||
const body = ReactDOMServer.renderToString(
|
||||
<ServerApp url={url} context={context} helmetContext={helmetContext} store={store} />
|
||||
);
|
||||
|
||||
const { helmet: head } = helmetContext;
|
||||
return { head, body };
|
||||
};
|
||||
|
||||
describe('Application - node environment', () => {
|
||||
it('renders in the server without crashing', () => {
|
||||
render('/', {});
|
||||
});
|
||||
|
||||
it('renders the styleguide without crashing', () => {
|
||||
render('/styleguide', {});
|
||||
});
|
||||
|
||||
it('server renders pages that do not require authentication', () => {
|
||||
const urlTitles = {
|
||||
'/': 'LandingPage.schemaTitle',
|
||||
'/s': 'SearchPage.schemaTitle',
|
||||
'/l/listing-title-slug/1234': 'ListingPage.loadingListingTitle',
|
||||
'/l/1234': 'ListingPage.loadingListingTitle',
|
||||
'/u/1234': 'ProfilePage.schemaTitle',
|
||||
'/login': 'AuthenticationPage.schemaTitleLogin',
|
||||
'/signup': 'AuthenticationPage.schemaTitleSignup',
|
||||
'/recover-password': 'PasswordRecoveryPage.title',
|
||||
'/this-url-should-not-be-found': 'NotFoundPage.title',
|
||||
'/reset-password?t=token&e=email': 'PasswordResetPage.title',
|
||||
};
|
||||
forEach(urlTitles, (title, url) => {
|
||||
const context = {};
|
||||
const { head, body } = render(url, context);
|
||||
|
||||
expect(head.title.toString()).toContain(title);
|
||||
|
||||
// context.url will contain the URL to redirect to if a <Redirect> was used
|
||||
expect(context.url).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('server renders redirects for pages that require authentication', () => {
|
||||
const loginPath = '/login';
|
||||
const signupPath = '/signup';
|
||||
const urlRedirects = {
|
||||
'/l/new': signupPath,
|
||||
'/l/listing-title-slug/1234/new/description': signupPath,
|
||||
'/l/listing-title-slug/1234/checkout': signupPath,
|
||||
'/profile-settings': loginPath,
|
||||
'/inbox': loginPath,
|
||||
'/inbox/orders': loginPath,
|
||||
'/inbox/sales': loginPath,
|
||||
'/order/1234': loginPath,
|
||||
'/order/1234/details': loginPath,
|
||||
'/sale/1234': loginPath,
|
||||
'/sale/1234/details': loginPath,
|
||||
'/listings': loginPath,
|
||||
'/account': loginPath,
|
||||
'/account/contact-details': loginPath,
|
||||
'/account/change-password': loginPath,
|
||||
'/account/payments': loginPath,
|
||||
'/verify-email': loginPath,
|
||||
};
|
||||
forEach(urlRedirects, (redirectPath, url) => {
|
||||
const context = {};
|
||||
const { body } = render(url, context);
|
||||
expect(context.url).toEqual(redirectPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to correct URLs', () => {
|
||||
const urlRedirects = { '/l': '/', '/u': '/' };
|
||||
forEach(urlRedirects, (redirectPath, url) => {
|
||||
const context = {};
|
||||
const { body } = render(url, context);
|
||||
expect(context.url).toEqual(redirectPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,21 +1,19 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import Helmet from 'react-helmet';
|
||||
import forEach from 'lodash/forEach';
|
||||
import { ClientApp, ServerApp } from './app';
|
||||
import { ClientApp } from './app';
|
||||
import configureStore from './store';
|
||||
|
||||
const render = (url, context) => {
|
||||
const store = configureStore();
|
||||
const body = ReactDOMServer.renderToString(
|
||||
<ServerApp url={url} context={context} store={store} />
|
||||
);
|
||||
const head = Helmet.peek();
|
||||
return { head, body };
|
||||
};
|
||||
const jsdomScroll = window.scroll;
|
||||
beforeAll(() => {
|
||||
// Mock window.scroll - otherwise, Jest/JSDOM will print a not-implemented error.
|
||||
window.scroll = () => {};
|
||||
});
|
||||
|
||||
describe('Application', () => {
|
||||
afterAll(() => {
|
||||
window.scroll = jsdomScroll;
|
||||
});
|
||||
|
||||
describe('Application - JSDOM environment', () => {
|
||||
it('renders in the client without crashing', () => {
|
||||
window.google = { maps: {} };
|
||||
const store = configureStore();
|
||||
|
|
@ -23,74 +21,4 @@ describe('Application', () => {
|
|||
ReactDOM.render(<ClientApp store={store} />, div);
|
||||
delete window.google;
|
||||
});
|
||||
|
||||
it('renders in the server without crashing', () => {
|
||||
render('/', {});
|
||||
});
|
||||
|
||||
it('renders the styleguide without crashing', () => {
|
||||
render('/styleguide', {});
|
||||
});
|
||||
|
||||
it('server renders pages that do not require authentication', () => {
|
||||
const urlTitles = {
|
||||
'/': 'LandingPage.schemaTitle',
|
||||
'/s': 'SearchPage.schemaTitle',
|
||||
'/l/listing-title-slug/1234': 'ListingPage.loadingListingTitle',
|
||||
'/l/1234': 'ListingPage.loadingListingTitle',
|
||||
'/u/1234': 'ProfilePage.schemaTitle',
|
||||
'/login': 'AuthenticationPage.schemaTitleLogin',
|
||||
'/signup': 'AuthenticationPage.schemaTitleSignup',
|
||||
'/recover-password': 'PasswordRecoveryPage.title',
|
||||
'/this-url-should-not-be-found': 'NotFoundPage.title',
|
||||
'/reset-password?t=token&e=email': 'PasswordResetPage.title',
|
||||
};
|
||||
forEach(urlTitles, (title, url) => {
|
||||
const context = {};
|
||||
const { head, body } = render(url, context);
|
||||
|
||||
expect(head.title).toContain(title);
|
||||
|
||||
// context.url will contain the URL to redirect to if a <Redirect> was used
|
||||
expect(context.url).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('server renders redirects for pages that require authentication', () => {
|
||||
const loginPath = '/login';
|
||||
const signupPath = '/signup';
|
||||
const urlRedirects = {
|
||||
'/l/new': signupPath,
|
||||
'/l/listing-title-slug/1234/new/description': signupPath,
|
||||
'/l/listing-title-slug/1234/checkout': signupPath,
|
||||
'/profile-settings': loginPath,
|
||||
'/inbox': loginPath,
|
||||
'/inbox/orders': loginPath,
|
||||
'/inbox/sales': loginPath,
|
||||
'/order/1234': loginPath,
|
||||
'/order/1234/details': loginPath,
|
||||
'/sale/1234': loginPath,
|
||||
'/sale/1234/details': loginPath,
|
||||
'/listings': loginPath,
|
||||
'/account': loginPath,
|
||||
'/account/contact-details': loginPath,
|
||||
'/account/change-password': loginPath,
|
||||
'/account/payments': loginPath,
|
||||
'/verify-email': loginPath,
|
||||
};
|
||||
forEach(urlRedirects, (redirectPath, url) => {
|
||||
const context = {};
|
||||
const { body } = render(url, context);
|
||||
expect(context.url).toEqual(redirectPath);
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to correct URLs', () => {
|
||||
const urlRedirects = { '/l': '/', '/u': '/' };
|
||||
forEach(urlRedirects, (redirectPath, url) => {
|
||||
const context = {};
|
||||
const { body } = render(url, context);
|
||||
expect(context.url).toEqual(redirectPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
BIN
src/assets/background-image.jpg
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
src/assets/colour-background.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
src/assets/ideas-image.jpg
Normal file
|
After Width: | Height: | Size: 234 KiB |