@colisweb/rescript-toolkit 2.67.0 → 2.67.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +3 -2
- package/.yarn/cache/boolbase-npm-1.0.0-965fe9af6d-3e25c80ef6.zip +0 -0
- package/.yarn/cache/camelcase-npm-3.0.0-0c65af0c7f-ae4fe1c17c.zip +0 -0
- package/.yarn/cache/comma-separated-tokens-npm-1.0.8-00dbbf3418-0adcb07174.zip +0 -0
- package/.yarn/cache/css-selector-parser-npm-1.4.1-b8c642c4c5-31948754e5.zip +0 -0
- package/.yarn/cache/fault-npm-2.0.1-c462630f58-c9b30f47d9.zip +0 -0
- package/.yarn/cache/format-npm-0.2.2-679f3acc64-646a60e133.zip +0 -0
- package/.yarn/cache/github-slugger-npm-2.0.0-3afba76e6c-250375cde2.zip +0 -0
- package/.yarn/cache/hast-util-has-property-npm-1.0.4-a09b607810-23025cee66.zip +0 -0
- package/.yarn/cache/hast-util-has-property-npm-2.0.1-aa6919669c-cc909b7e29.zip +0 -0
- package/.yarn/cache/hast-util-heading-rank-npm-2.1.1-0d71da5801-a49233e9ac.zip +0 -0
- package/.yarn/cache/hast-util-is-element-npm-1.1.0-be10e62fa7-30fad3f65e.zip +0 -0
- package/.yarn/cache/hast-util-is-element-npm-2.1.3-3051d610ff-9d988f6839.zip +0 -0
- package/.yarn/cache/hast-util-select-npm-1.0.1-115974f390-9cdb20850f.zip +0 -0
- package/.yarn/cache/hast-util-to-string-npm-2.0.0-c6108aa2b8-0c087f8dee.zip +0 -0
- package/.yarn/cache/hast-util-to-text-npm-3.1.2-922eb1f623-d17cf3344c.zip +0 -0
- package/.yarn/cache/hast-util-whitespace-npm-1.0.4-43bb1ff3d0-b7f4a1942b.zip +0 -0
- package/.yarn/cache/{husky-npm-8.0.2-46c70b41ed-e101656fcb.zip → husky-npm-8.0.3-b0b59c5127-837bc7e441.zip} +0 -0
- package/.yarn/cache/lowlight-npm-2.8.0-c65abb6cac-c45a91e715.zip +0 -0
- package/.yarn/cache/not-npm-0.1.0-a1712708cd-8043bb53bc.zip +0 -0
- package/.yarn/cache/nth-check-npm-1.0.2-3f6d0d22eb-59e115fdd7.zip +0 -0
- package/.yarn/cache/property-information-npm-3.2.0-ae434c241d-ed2614520d.zip +0 -0
- package/.yarn/cache/rehype-add-classes-npm-1.0.0-ddf6b4e74d-25c0e2dbf5.zip +0 -0
- package/.yarn/cache/rehype-autolink-headings-npm-6.1.1-fe8058cc11-60782fb2e5.zip +0 -0
- package/.yarn/cache/rehype-highlight-npm-6.0.0-5179257139-5a70e7ad45.zip +0 -0
- package/.yarn/cache/rehype-slug-npm-5.1.0-ae08840ba8-2a7c17fd74.zip +0 -0
- package/.yarn/cache/space-separated-tokens-npm-1.1.5-2352c83473-8ef68f1cfa.zip +0 -0
- package/.yarn/cache/unist-util-find-after-npm-4.0.0-11b3b7fb4f-8381ef0bad.zip +0 -0
- package/.yarn/cache/zwitch-npm-1.0.5-5911cef6ce-28a1bebaca.zip +0 -0
- package/.yarn/install-state.gz +0 -0
- package/package.json +8 -5
- package/playground/{App.res → PlaygroundApp.res} +14 -7
- package/playground/PlaygroundComponents.res +109 -0
- package/playground/PlaygroundLocales.res +1 -0
- package/playground/PlaygroundRouter.res +23 -0
- package/playground/{CodeBlock.res → Playground_CodeBlock.res} +12 -16
- package/playground/{stories/IntroductionColors.stories.mdx → design/DesignSystem_Colors.mdx} +0 -0
- package/playground/{stories/IntroductionFonts.stories.mdx → design/DesignSystem_Fonts.mdx} +0 -0
- package/playground/{stories/IntroductionMediaQueries.stories.mdx → design/DesignSystem_MediaQueries.mdx} +0 -0
- package/playground/design/Playground_DesignSystem.res +12 -0
- package/playground/{stories/TailwindConfigBreakpoints.js → design/TailwindConfigBreakpoints.jsx} +1 -1
- package/playground/{stories/TailwindConfigColorsPreview.js → design/TailwindConfigColorsPreview.jsx} +1 -1
- package/playground/{stories/TailwindConfigFontsPreview.js → design/TailwindConfigFontsPreview.jsx} +1 -1
- package/playground/docs/ApiDecoding.mdx +70 -0
- package/playground/docs/Form.mdx +109 -0
- package/playground/docs/Identifiers.mdx +38 -0
- package/playground/{Doc.res → docs/Playground_Docs.res} +8 -8
- package/playground/docs/Primitives.mdx +53 -0
- package/playground/docs/Request.mdx +281 -0
- package/playground/docs/Router.mdx +43 -0
- package/playground/docs/Setup.mdx +58 -0
- package/playground/docs/Unleash.mdx +53 -0
- package/playground/main.jsx +1 -1
- package/src/intl/Toolkit__Intl.res +5 -9
- package/src/intl/Toolkit__Intl.resi +1 -0
- package/src/logger/Toolkit__BrowserLogger.res +15 -15
- package/src/tailwind/tailwind.config.cjs +3 -5
- package/vite.config.js +43 -1
- package/.yarn/cache/cosmiconfig-npm-7.0.0-b9d0d7d156-6801feaa02.zip +0 -0
- package/.yarn/cache/import-fresh-npm-3.3.0-3e34265ca9-2cacfad06e.zip +0 -0
- package/.yarn/cache/klona-npm-2.0.4-6bc4e7cd86-abc6690882.zip +0 -0
- package/.yarn/cache/postcss-loader-npm-4.2.0-f01fec2503-c45ec1ca1b.zip +0 -0
- package/.yarn/cache/semver-npm-7.3.4-4c3baf0ead-96451bfd7c.zip +0 -0
- package/playground/Playground_Component.res +0 -33
- package/playground/Playground_ComponentDetails.res +0 -25
- package/playground/Playground_ComponentsList.res +0 -43
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
module ApiDecoding = {
|
|
2
|
-
@module("
|
|
2
|
+
@module("../../../../playground/docs/ApiDecoding.mdx") @react.component
|
|
3
3
|
external make: unit => React.element = "default"
|
|
4
4
|
}
|
|
5
5
|
module Form = {
|
|
6
|
-
@module("
|
|
6
|
+
@module("../../../../playground/docs/Form.mdx") @react.component
|
|
7
7
|
external make: unit => React.element = "default"
|
|
8
8
|
}
|
|
9
9
|
module Identifiers = {
|
|
10
|
-
@module("
|
|
10
|
+
@module("../../../../playground/docs/Identifiers.mdx") @react.component
|
|
11
11
|
external make: unit => React.element = "default"
|
|
12
12
|
}
|
|
13
13
|
module Primitives = {
|
|
14
|
-
@module("
|
|
14
|
+
@module("../../../../playground/docs/Primitives.mdx") @react.component
|
|
15
15
|
external make: unit => React.element = "default"
|
|
16
16
|
}
|
|
17
17
|
module Requests = {
|
|
18
|
-
@module("
|
|
18
|
+
@module("../../../../playground/docs/Request.mdx") @react.component
|
|
19
19
|
external make: unit => React.element = "default"
|
|
20
20
|
}
|
|
21
21
|
module Setup = {
|
|
22
|
-
@module("
|
|
22
|
+
@module("../../../../playground/docs/Setup.mdx") @react.component
|
|
23
23
|
external make: unit => React.element = "default"
|
|
24
24
|
}
|
|
25
25
|
module Router = {
|
|
26
|
-
@module("
|
|
26
|
+
@module("../../../../playground/docs/Router.mdx") @react.component
|
|
27
27
|
external make: unit => React.element = "default"
|
|
28
28
|
}
|
|
29
29
|
module Unleash = {
|
|
30
|
-
@module("
|
|
30
|
+
@module("../../../../playground/docs/Unleash.mdx") @react.component
|
|
31
31
|
external make: unit => React.element = "default"
|
|
32
32
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Primitives
|
|
2
|
+
|
|
3
|
+
Utililties functions for primitives types :
|
|
4
|
+
|
|
5
|
+
- `option`
|
|
6
|
+
- `string`
|
|
7
|
+
- `array`
|
|
8
|
+
|
|
9
|
+
## Option
|
|
10
|
+
|
|
11
|
+
### string
|
|
12
|
+
|
|
13
|
+
- `fromString` : `string => option(string)`
|
|
14
|
+
- `toString` : `option(string) => string`
|
|
15
|
+
|
|
16
|
+
```rescript
|
|
17
|
+
let anEmptyString = "";
|
|
18
|
+
|
|
19
|
+
Toolkit.Primitives.Option.fromString(anEmptyString)
|
|
20
|
+
->Option.map(v => v ++ "test");
|
|
21
|
+
|
|
22
|
+
Toolkit.Primitives.Option.toString(Some("test")); // "test"
|
|
23
|
+
Toolkit.Primitives.Option.toString(None); // ""
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### boolean
|
|
27
|
+
|
|
28
|
+
- `fromBool` : `bool => option(bool)`
|
|
29
|
+
|
|
30
|
+
```rescript
|
|
31
|
+
Toolkit.Primitives.Option.fromBool(true); // Some(true)
|
|
32
|
+
Toolkit.Primitives.Option.fromBool(false); // None
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Array
|
|
36
|
+
|
|
37
|
+
```rescript
|
|
38
|
+
let variable = ["abc", "dce", ""];
|
|
39
|
+
|
|
40
|
+
Js.log(Primitives.Array.joinNonEmpty(anEmptyString)); // "abc, dce"
|
|
41
|
+
Js.log(Primitives.Array.joinNonEmpty(~separator="+", anEmptyString)); // "abc+dce"
|
|
42
|
+
Js.log(variable->Primitives.Array.tail); // ""
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Result
|
|
46
|
+
|
|
47
|
+
```rescript
|
|
48
|
+
let result = Ok("test");
|
|
49
|
+
let test = Error("error");
|
|
50
|
+
|
|
51
|
+
Js.log(Primitives.Result.get(result)); // Some("test")
|
|
52
|
+
Js.log(Primitives.Result.get(test)); // None
|
|
53
|
+
```
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# How to manage an API request
|
|
2
|
+
|
|
3
|
+
## 1. Create the request config
|
|
4
|
+
|
|
5
|
+
You have to declare 3 types :
|
|
6
|
+
|
|
7
|
+
- `argument` is what you give to the function
|
|
8
|
+
- `response` is what your API answer
|
|
9
|
+
- `error` is the potential custom errors that can occur (⚠️ this has to be handled manually)
|
|
10
|
+
|
|
11
|
+
And the `exec` method.
|
|
12
|
+
|
|
13
|
+
We use [decco](https://github.com/reasonml-labs/decco) to serialize / deserialize the data.
|
|
14
|
+
|
|
15
|
+
```rescript
|
|
16
|
+
let request = Axios.Instance.create(
|
|
17
|
+
Axios.makeConfig(~baseURL=envState.apiUrl,()),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
module FetchUsers = {
|
|
21
|
+
module Config = {
|
|
22
|
+
type argument = unit;
|
|
23
|
+
|
|
24
|
+
@decco
|
|
25
|
+
type response = array<user>
|
|
26
|
+
@decco
|
|
27
|
+
and user = {
|
|
28
|
+
name: string
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type error = string;
|
|
32
|
+
|
|
33
|
+
let exec = () => {
|
|
34
|
+
request->Request.get("/", response_decode);
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
module Request = Toolkit.Request.Make(Config);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
FetchUsers.Request.exec();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 1.2 Send data
|
|
44
|
+
|
|
45
|
+
We create a decodable `body` type that we give to the `argument` type.
|
|
46
|
+
This decodable `body` will be used in the exec method.
|
|
47
|
+
|
|
48
|
+
```rescript
|
|
49
|
+
module UpdateUser = {
|
|
50
|
+
module Config = {
|
|
51
|
+
type argument = body
|
|
52
|
+
@decco
|
|
53
|
+
and body = {
|
|
54
|
+
name: string,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
@decco
|
|
58
|
+
type response = unit;
|
|
59
|
+
|
|
60
|
+
type error = unit;
|
|
61
|
+
|
|
62
|
+
let exec = body => {
|
|
63
|
+
request->Request.postData("/update-user", response_decode, body->body_encode);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
module Request = Toolkit_Request.Make(Config);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
UpdateUser.Request.exec({ name: "test" })
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 1.3 Handle errors
|
|
73
|
+
|
|
74
|
+
In order to handle special errors comming from the API, you can use the `mapError` parameter.
|
|
75
|
+
|
|
76
|
+
This parameter requires a callback which contains the **response** as argument and needs to return this type :
|
|
77
|
+
|
|
78
|
+
```rescript
|
|
79
|
+
option<result<'data, error<'a>>>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The `result` is useful for some cases when an API error should not break the flow so you can handle the specific error and just return `Ok()`.
|
|
83
|
+
|
|
84
|
+
#### Request error type
|
|
85
|
+
|
|
86
|
+
```rescript
|
|
87
|
+
type undecodedResponse = {
|
|
88
|
+
data: undecodedData,
|
|
89
|
+
headers,
|
|
90
|
+
config,
|
|
91
|
+
status: int,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
type noResponse = {
|
|
95
|
+
message: string,
|
|
96
|
+
config,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// We get this type in mapError callback argument
|
|
100
|
+
type failedResponse = {
|
|
101
|
+
message: string,
|
|
102
|
+
config,
|
|
103
|
+
response,
|
|
104
|
+
}
|
|
105
|
+
and response = {
|
|
106
|
+
data: failedResponseData,
|
|
107
|
+
headers,
|
|
108
|
+
status: int,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
type error<'a> = [
|
|
112
|
+
| #noResponse(noResponse)
|
|
113
|
+
| #invalidResponse(failedResponse)
|
|
114
|
+
| #invalidResponseData(undecodedResponse, string)
|
|
115
|
+
| #invalidErrorData
|
|
116
|
+
| #unknown(Js.Promise.error)
|
|
117
|
+
| #custom('a)
|
|
118
|
+
]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```rescript
|
|
122
|
+
let exec = token => {
|
|
123
|
+
request->Request.get(
|
|
124
|
+
"/test",
|
|
125
|
+
~mapError=
|
|
126
|
+
error => {
|
|
127
|
+
// have to return option(Toolkit.Request.error('a'))
|
|
128
|
+
},
|
|
129
|
+
response_decode,
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`failedResponseData` can be anything depending on the API behavior. So you have to use `Obj.magic` to cast and use the correct field.
|
|
135
|
+
|
|
136
|
+
```rescript
|
|
137
|
+
let handleError = (error: failedResponse) => {
|
|
138
|
+
// the API return an object { code: string }
|
|
139
|
+
let code = error.response.data->Obj.magic##code;
|
|
140
|
+
|
|
141
|
+
// If code can be undefined
|
|
142
|
+
code
|
|
143
|
+
->Option.map(code => {
|
|
144
|
+
switch (code) {
|
|
145
|
+
| "some_code" => Error(#custom("message"))
|
|
146
|
+
| "other_code" => Error(#invalidErrorData)
|
|
147
|
+
| "its_ok" => Ok()
|
|
148
|
+
| _ => Error(#custom("unknown"))
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
};
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Often, we use decco in order to handle complex error (error with data). We create a custom Decoder for this kind of case
|
|
155
|
+
|
|
156
|
+
```rescript
|
|
157
|
+
module UpdateUser = {
|
|
158
|
+
module Config = {
|
|
159
|
+
type argument = body
|
|
160
|
+
@decco
|
|
161
|
+
and body = {name: string};
|
|
162
|
+
|
|
163
|
+
@decco
|
|
164
|
+
type response = unit;
|
|
165
|
+
|
|
166
|
+
module Error =
|
|
167
|
+
Decoders.Enum({
|
|
168
|
+
@deriving(jsConverter)
|
|
169
|
+
type enum = [
|
|
170
|
+
| #"unknown-user"
|
|
171
|
+
| #invalidData
|
|
172
|
+
| #unknownError
|
|
173
|
+
];
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
@decco
|
|
177
|
+
type error = Error.t;
|
|
178
|
+
|
|
179
|
+
let exec = body => {
|
|
180
|
+
request->Request.postData(
|
|
181
|
+
"/update-user",
|
|
182
|
+
response_decode,
|
|
183
|
+
body->body_encode,
|
|
184
|
+
~mapError=
|
|
185
|
+
({response}) => {
|
|
186
|
+
switch (response.data->Obj.magic->error_decode) {
|
|
187
|
+
| Ok(error) => Some(Error(`custom(error)))
|
|
188
|
+
| Error(err) =>
|
|
189
|
+
Js.log(err);
|
|
190
|
+
Some(Error(`custom(`unknownError)));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
);
|
|
194
|
+
};
|
|
195
|
+
};
|
|
196
|
+
module Request = Toolkit_Request.Make(Config);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
UpdateUser.Request.exec({ name: "test" })
|
|
200
|
+
->Promise.tapError(err => {
|
|
201
|
+
switch (err) {
|
|
202
|
+
| #custom(#"unknown-user") => Js.log("user not found")
|
|
203
|
+
| #custom(#unknownError) => Js.log("alert sentry")
|
|
204
|
+
| _ => Js.log("generic handler")
|
|
205
|
+
}
|
|
206
|
+
})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## 2. Use request in React
|
|
210
|
+
|
|
211
|
+
### 2.1 Fetching data with swr
|
|
212
|
+
|
|
213
|
+
There are several ways to manage a request in React, we chose to use [swr](https://swr.vercel.app/) with [react-suspense](https://reactjs.org/docs/concurrent-mode-suspense.html) enabled.
|
|
214
|
+
We have a functor to generate automaticatly a swr fetcher based on the request created before :
|
|
215
|
+
|
|
216
|
+
```rescript
|
|
217
|
+
module GetUsers = {
|
|
218
|
+
module Config = {
|
|
219
|
+
type argument = unit;
|
|
220
|
+
|
|
221
|
+
@decco
|
|
222
|
+
type response = array<user>
|
|
223
|
+
@decco
|
|
224
|
+
and user = {
|
|
225
|
+
name: string
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
type error = string;
|
|
229
|
+
|
|
230
|
+
let exec = () => {
|
|
231
|
+
request->Request.get("/users", response_decode);
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
module Request = Toolkit.Request.Make();
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
module GetUsersFetcher =
|
|
239
|
+
Fetcher.Make({
|
|
240
|
+
module Request = GetUsers.Request;
|
|
241
|
+
let key = () => [|
|
|
242
|
+
"GetUsers",
|
|
243
|
+
|];
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Later in the react app
|
|
248
|
+
|
|
249
|
+
```rescript
|
|
250
|
+
module PageUsers = {
|
|
251
|
+
[@react.component]
|
|
252
|
+
let make = () => {
|
|
253
|
+
let (users, _, _) = GetUsersFetcher.use();
|
|
254
|
+
|
|
255
|
+
<div>
|
|
256
|
+
{
|
|
257
|
+
users
|
|
258
|
+
->Belt.Array.mapWithIndex((i, user) => {
|
|
259
|
+
<div key={i->Belt.Int.toString}>
|
|
260
|
+
{user.name->React.String}
|
|
261
|
+
</div>
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
</div>
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
[@react.component]
|
|
269
|
+
let make = () => {
|
|
270
|
+
<ErrorBoundary>
|
|
271
|
+
<React.Suspense fallback={<Spinner />}>
|
|
272
|
+
<PageUsers />
|
|
273
|
+
</React.Suspense>
|
|
274
|
+
</ErrorBoundary>
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
There are 2 functions generated by the fetcher functor :
|
|
279
|
+
|
|
280
|
+
- `use` can be used when the API will always return something
|
|
281
|
+
- `useOptional` return an `option<'a>` so it's used when the API can return nothing
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Router
|
|
2
|
+
|
|
3
|
+
Type-safe navigation
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```rescript
|
|
8
|
+
open Toolkit
|
|
9
|
+
|
|
10
|
+
module StoreId = Identifiers.MakeString()
|
|
11
|
+
|
|
12
|
+
module RouterConfig = {
|
|
13
|
+
type admin = Home
|
|
14
|
+
|
|
15
|
+
type t =
|
|
16
|
+
| Home
|
|
17
|
+
| StoreDetails(StoreId.t)
|
|
18
|
+
| Admin(admin)
|
|
19
|
+
| NotFound
|
|
20
|
+
|
|
21
|
+
let make = url =>
|
|
22
|
+
switch url.path {
|
|
23
|
+
| list{} => Home
|
|
24
|
+
| list{"stores", storeId} => StoreDetails(StoreId.make(storeId))
|
|
25
|
+
| list{"admin", ...rest} =>
|
|
26
|
+
switch rest {
|
|
27
|
+
| list{} => Admin(Home)
|
|
28
|
+
| _ => NotFound
|
|
29
|
+
}
|
|
30
|
+
| _ => NotFound
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let toString = route =>
|
|
34
|
+
switch route {
|
|
35
|
+
| Home => ""
|
|
36
|
+
| StoreDetails(storeId) => "/stores/" ++ storeId->StoreId.toString
|
|
37
|
+
| Admin(Home) => "/admin/"
|
|
38
|
+
| NotFound => "/404"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module Navigation = Router.Make(RouterConfig)
|
|
43
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ReScript toolkit
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
Install the package :
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -S @colisweb/rescript-toolkit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Add the package to the `bsconfig.json` file :
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"bs-dependencies": ["@colisweb/rescript-toolkit"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Import the style in your app :
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
// App.js
|
|
23
|
+
import "@colisweb/rescript-toolkit/src/ui/styles.css";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Add Tailwind
|
|
27
|
+
|
|
28
|
+
This toolkit is based on [tailwindcss](https://tailwindcss.com/).
|
|
29
|
+
We share a postcss config that you can include to your webpack config
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
The bindings in the _vendors_ are **globaly accessible**. Everything else is scoped into the `Toolkit` namespace.
|
|
34
|
+
|
|
35
|
+
```rescript
|
|
36
|
+
open Toolkit;
|
|
37
|
+
|
|
38
|
+
@react.component
|
|
39
|
+
let make = () => {
|
|
40
|
+
let dialog = Hooks.useDisclosure();
|
|
41
|
+
|
|
42
|
+
<div className=Tw.(style([bgWhite])) />
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Local development
|
|
47
|
+
|
|
48
|
+
Run bsb
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run dev
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run storybook
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm run storybook
|
|
58
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Unleash
|
|
2
|
+
|
|
3
|
+
[Unleash](https://github.com/Unleash/unleash) is feature flipping solution. It allows us to activate or restrain fonctionalities acording a configuration.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### 1. Create a rule
|
|
8
|
+
|
|
9
|
+
We use a functor to enforce several parameters :
|
|
10
|
+
|
|
11
|
+
| Params | value |
|
|
12
|
+
| ----------- | :--------------------------------------------: |
|
|
13
|
+
| parameters | 'parameters |
|
|
14
|
+
| input | 'input |
|
|
15
|
+
| output | 'output |
|
|
16
|
+
| envUrl | string |
|
|
17
|
+
| featureName | string |
|
|
18
|
+
| exec | ('input, Unleash.config(parameters)) => output |
|
|
19
|
+
|
|
20
|
+
```rescript
|
|
21
|
+
module OutdatedApplicationConfig = {
|
|
22
|
+
@decco
|
|
23
|
+
type parameters = {version: string}
|
|
24
|
+
type input = unit
|
|
25
|
+
type output = bool
|
|
26
|
+
let envUrl = Env.unleashUrl
|
|
27
|
+
let featureName = "minimum-supported-version"
|
|
28
|
+
|
|
29
|
+
let exec = (_, config: Unleash.config<parameters>) => {
|
|
30
|
+
let appVersion =
|
|
31
|
+
ReactNativeVersionNumber.appVersion->Js.Nullable.toOption->Option.getWithDefault("")
|
|
32
|
+
|
|
33
|
+
config.strategies
|
|
34
|
+
->Array.get(0)
|
|
35
|
+
->Option.mapWithDefault(false, strategy =>
|
|
36
|
+
config.enabled ? Semver.lt(appVersion, strategy.parameters.version) : false
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module OutdatedApplication = Toolkit.Unleash.MakeFeature(OutdatedApplicationConfig)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Use the rule
|
|
45
|
+
|
|
46
|
+
```rescript
|
|
47
|
+
OutdatedApplication.exec()
|
|
48
|
+
->Promise.tapOk(outdated => {
|
|
49
|
+
if (outdated) {
|
|
50
|
+
Js.log("app outdated");
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
```
|
package/playground/main.jsx
CHANGED
|
@@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
|
|
|
3
3
|
import "../src/ui/styles.css";
|
|
4
4
|
import "../src/ui/Toolkit__Ui_DatetimeInput.css";
|
|
5
5
|
import "./custom.css";
|
|
6
|
-
import App from "../lib/es6_global/playground/
|
|
6
|
+
import App from "../lib/es6_global/playground/PlaygroundApp.bs";
|
|
7
7
|
|
|
8
8
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
|
9
9
|
|
|
@@ -31,16 +31,11 @@ let availableLanguagesFromString = v =>
|
|
|
31
31
|
| _ => #fr
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
let createIntl = (locale: availableLanguages, messages) => {
|
|
34
|
+
let createIntl = (~onError=?, locale: availableLanguages, messages) => {
|
|
35
35
|
let cache = createIntlCache()
|
|
36
36
|
|
|
37
37
|
createIntl(
|
|
38
|
-
intlConfig(
|
|
39
|
-
~locale=locale->availableLanguagesToString,
|
|
40
|
-
~messages,
|
|
41
|
-
~onError=message => Toolkit__BrowserLogger.error2("create intl error", message),
|
|
42
|
-
(),
|
|
43
|
-
),
|
|
38
|
+
intlConfig(~locale=locale->availableLanguagesToString, ~messages, ~onError?, ()),
|
|
44
39
|
cache,
|
|
45
40
|
)
|
|
46
41
|
}
|
|
@@ -48,6 +43,7 @@ let createIntl = (locale: availableLanguages, messages) => {
|
|
|
48
43
|
module type IntlConfig = {
|
|
49
44
|
let messages: messages
|
|
50
45
|
let defaultLocale: option<availableLanguages>
|
|
46
|
+
let onError: string => unit
|
|
51
47
|
}
|
|
52
48
|
|
|
53
49
|
module Make = (Config: IntlConfig) => {
|
|
@@ -67,10 +63,10 @@ module Make = (Config: IntlConfig) => {
|
|
|
67
63
|
|
|
68
64
|
type action = SetLocale(availableLanguages)
|
|
69
65
|
|
|
70
|
-
let store = Restorative.createStore({locale
|
|
66
|
+
let store = Restorative.createStore({locale, intl}, (_state, action) =>
|
|
71
67
|
switch action {
|
|
72
68
|
| SetLocale(locale) => {
|
|
73
|
-
locale
|
|
69
|
+
locale,
|
|
74
70
|
intl: {
|
|
75
71
|
let messages = switch locale {
|
|
76
72
|
| #fr => Config.messages.fr->toDict
|
|
@@ -51,22 +51,22 @@ let warning3 = (str, str2, str3) => {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
let error = (str: string) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
54
|
+
DatadogRum.Browser.addError(makeError(str))
|
|
55
|
+
// if nodeEnv !== "production" {
|
|
56
|
+
// Js.Console.log3("%cERROR", errorStyle, str)
|
|
57
|
+
// } else {
|
|
58
|
+
// }
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
let error2 = (str, str2) => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
62
|
+
DatadogRum.Browser.addErrorWithContext(
|
|
63
|
+
makeError(str),
|
|
64
|
+
{
|
|
65
|
+
"cause": str2,
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
// if nodeEnv !== "production" {
|
|
69
|
+
// Js.Console.log4("%cERROR", errorStyle, str, str2)
|
|
70
|
+
// } else {
|
|
71
|
+
// }
|
|
72
72
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const defaultColors = require("tailwindcss/colors");
|
|
2
|
-
|
|
3
1
|
module.exports = {
|
|
4
2
|
theme: {
|
|
5
3
|
extend: {
|
|
@@ -65,9 +63,9 @@ module.exports = {
|
|
|
65
63
|
screen: "100vh",
|
|
66
64
|
},
|
|
67
65
|
colors: {
|
|
68
|
-
white:
|
|
69
|
-
black:
|
|
70
|
-
transparent:
|
|
66
|
+
white: "#fff",
|
|
67
|
+
black: "#000",
|
|
68
|
+
transparent: "transparent",
|
|
71
69
|
routeWithdraw: "#176693",
|
|
72
70
|
gray: {
|
|
73
71
|
100: "#f7fafc",
|