Skip to content

Commit 45281ea

Browse files
authored
refactor: ⬆️ migrate to Strapi 5 (#5)
* refactor: ⬆️ migrate to Strapi 5 * remove dependency on moment.js * Merge branch 'main' into strapi5-migr
1 parent 7356af0 commit 45281ea

File tree

14 files changed

+8895
-4201
lines changed

14 files changed

+8895
-4201
lines changed

.editorconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
root = true
2+
3+
[*]
4+
indent_style = space
5+
indent_size = 2
6+
end_of_line = lf
7+
charset = utf-8
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
11+
[{package.json,*.yml}]
12+
indent_style = space
13+
indent_size = 2
14+
15+
[*.md]
16+
trim_trailing_whitespace = false

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Cédric Pontet
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 17 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,46 @@
11
# Strapi plugin timezone-select
22

3-
A strapi custom field for selecting any timezone based on the [moment.js](https://momentjs.com/) timezones.
3+
A Strapi custom field to select a Olson/IANA time zone.
4+
5+
Examples:
6+
7+
- Africa/Ndjamena
8+
- America/New_York,
9+
- Asia/Bangkok,
10+
- Europe/Paris,
11+
- Oceania/Sydney
412

513
## Installation
614

715
To install this plugin, you need to add an NPM dependency to your Strapi application:
816

9-
```sh
10-
# Using yarn
17+
```
18+
# Using Yarn
1119
yarn add strapi-plugin-timezone-select
1220
13-
# Using npm
21+
# Or using NPM
1422
npm install strapi-plugin-timezone-select
15-
16-
# Using pnpm
17-
pnpm add strapi-plugin-timezone-select
1823
```
1924

2025
Then, you'll need to build your admin panel:
2126

22-
```sh
23-
# Using yarn
27+
```
28+
# Using Yarn
2429
yarn build
2530
26-
# Using npm
31+
# Or using NPM
2732
npm run build
28-
29-
# Using pnpm
30-
pnpm build
3133
```
3234

3335
## Usage
3436

3537
After installation you will find the timezone-select at the custom fields section of the content-type builder.
3638

37-
![timezone select screenshot](/assets/timezone-select-custom-field.png)
39+
![timezone select screenshot](./timezone-select-custom-field.png)
3840

3941
Now you can select any country from the list. The Alpha-2 code of the selected timezone is stored in the database.
4042

41-
![timezone select screenshot](/assets/timezone-select.png)
42-
43-
## Development
44-
45-
### Plugin creation
46-
47-
This plugin was created using [Strapi 5 plugin SDK](https://docs.strapi.io/dev-docs/plugins/development/plugin-sdk)
48-
49-
```sh
50-
# Using yarn
51-
yarn dlx @strapi/sdk-plugin init strapi-plugin-timezone-select
52-
53-
# Using npm
54-
npx @strapi/sdk-plugin init strapi-plugin-timezone-select
55-
56-
# Using pnpm
57-
pnpm dlx @strapi/sdk-plugin init strapi-plugin-timezone-select
58-
```
59-
60-
### Start watch mode on the plugin
61-
62-
To start working on your plugin
63-
64-
- Open a terminal
65-
- Navigate to your plugin folder `strapi-plugin-timezone-select`
66-
- Run the following command
67-
68-
```sh
69-
# Using yarn
70-
yarn watch:link
71-
72-
# Using npm
73-
npm run watch:link
74-
75-
# Using pnpm
76-
pnpm watch:link
77-
```
78-
79-
### Link the plugin to your Strapi project
80-
81-
To link the plugin to your Strapi project
82-
83-
- Open a terminal
84-
- Navigate to your Strapi project
85-
- Run the following commands
86-
87-
```sh
88-
# Using yarn
89-
yarn dlx yalc add --link strapi-plugin-timezone-select
90-
yarn install
91-
92-
# Using npm
93-
npx yalc add strapi-plugin-timezone-select
94-
npx yalc link strapi-plugin-timezone-select
95-
npm install
96-
97-
# Using pnpm
98-
pnpm dlx yalc add --link strapi-plugin-timezone-select
99-
pnpm install
100-
101-
```
43+
![timezone select screenshot](./timezone-select.png)
10244

10345
## Related
10446

Lines changed: 62 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,68 @@
1-
import { Field, SingleSelect, SingleSelectOption } from '@strapi/design-system';
2-
import { useEffect, useState } from 'react';
1+
import { Combobox, ComboboxOption, Field } from '@strapi/design-system';
2+
import { Clock } from '@strapi/icons';
3+
import { useField, type FieldValue, type InputProps } from '@strapi/strapi/admin';
4+
import React from 'react';
35
import { useIntl } from 'react-intl';
4-
import moment from 'moment-timezone';
6+
import { getTranslation } from '../utils/getTranslation';
57

6-
export const TimezoneSelect = ({
7-
value,
8-
onChange,
9-
name,
10-
intlLabel,
11-
error,
12-
}: {
13-
value: string | null;
14-
onChange: (name: string, value: string | null) => void;
15-
name: string;
16-
intlLabel: { id: string; defaultMessage: string };
17-
error?: string;
18-
}) => {
19-
const { formatMessage } = useIntl();
20-
const label = formatMessage(intlLabel);
21-
const errorMessage = error ? formatMessage({ id: error, defaultMessage: error }) : '';
8+
const timezones = Intl.supportedValuesOf('timeZone'); // Outputs: ["Africa/Abidjan", "Africa/Accra", ...]
9+
const currentTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
2210

23-
const [timezones, setTimezones] = useState<Array<{ value: string; label: string }>>([]);
11+
type TimezoneSelectProps = InputProps & FieldValue;
2412

25-
useEffect(() => {
26-
const zones = moment.tz.names().map((zone) => ({
27-
value: zone,
28-
label: `${zone} (${moment.tz(zone).format('Z')})`,
29-
}));
30-
setTimezones(zones.sort((a, b) => a.label.localeCompare(b.label)));
31-
}, []);
13+
const TimezoneSelect = React.forwardRef<HTMLDivElement, TimezoneSelectProps>(
14+
({
15+
name,
16+
value,
17+
hint,
18+
required,
19+
disabled,
20+
error,
21+
}, forwardedRef) => {
22+
const { formatMessage } = useIntl();
23+
const field = useField(name);
3224

33-
return (
34-
<Field.Root error={errorMessage} name={name}>
35-
<Field.Label>{label}</Field.Label>
36-
<SingleSelect
37-
onChange={(timezone: string) => onChange(name, timezones.find((tz) => tz.value === timezone)?.value || null)}
38-
onClear={() => onChange(name, null)}
39-
value={value || ''}
25+
return (
26+
<Field.Root
27+
id={name}
28+
name={name}
29+
error={error}
30+
required={required}
31+
hint={hint}
4032
>
41-
{timezones.map(({ value, label }) => (
42-
<SingleSelectOption key={value} value={value}>
43-
{label}
44-
</SingleSelectOption>
45-
))}
46-
</SingleSelect>
47-
<Field.Error />
48-
</Field.Root>
49-
);
50-
};
33+
<Field.Label>
34+
{name}
35+
</Field.Label>
36+
<Combobox
37+
label={formatMessage({
38+
id: getTranslation('label'),
39+
defaultMessage: 'Select a time zone',
40+
})}
41+
placeholder={formatMessage({
42+
id: getTranslation('placeholder'),
43+
defaultMessage: 'Select a time zone or start typing the name of a city',
44+
})}
45+
aria-label={formatMessage({
46+
id: getTranslation('aria-label'),
47+
defaultMessage: 'Select a time zone',
48+
})}
49+
aria-disabled={disabled}
50+
disabled={disabled}
51+
startIcon={<Clock />}
52+
value={value ?? currentTimeZone}
53+
clearLabel="Clear"
54+
onClear={() => { field.onChange(name, "") }}
55+
onChange={(timezone: string) => { field.onChange(name, timezone) }}
56+
autocomplete={{ type: 'list', filter: 'contains' }}
57+
>
58+
{timezones.map((timezone) => (
59+
<ComboboxOption key={timezone} value={timezone}>{timezone}</ComboboxOption>
60+
))}
61+
</Combobox>
62+
<Field.Error />
63+
<Field.Hint />
64+
</Field.Root>
65+
)
66+
})
67+
68+
export default TimezoneSelect;

admin/src/index.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { getTranslation } from './utils/getTranslation';
2-
import { PLUGIN_ID } from './pluginId';
31
import { Initializer } from './components/Initializer';
4-
import { PluginIcon } from './components/PluginIcon';
52
import TimezoneSelectIcon from './components/TimezoneSelectIcon';
3+
import { PLUGIN_ID } from './pluginId';
4+
import { getTranslation } from './utils/getTranslation';
65

76
export default {
87
register(app: any) {
9-
108
app.customFields.register({
119
name: 'timezone',
12-
pluginId: PLUGIN_ID,
10+
plugnId: PLUGIN_ID,
1311
type: 'string',
1412
icon: TimezoneSelectIcon,
1513
intlLabel: {
@@ -18,27 +16,11 @@ export default {
1816
},
1917
intlDescription: {
2018
id: getTranslation('description'),
21-
defaultMessage: 'Select any time zone',
19+
defaultMessage: 'Select a time zone',
2220
},
2321
components: {
2422
Input: async () => import('./components/TimezoneSelect'),
2523
},
26-
options: {},
27-
});
28-
29-
30-
app.addMenuLink({
31-
to: `plugins/${PLUGIN_ID}`,
32-
icon: PluginIcon,
33-
intlLabel: {
34-
id: `${PLUGIN_ID}.plugin.name`,
35-
defaultMessage: PLUGIN_ID,
36-
},
37-
Component: async () => {
38-
const { App } = await import('./pages/App');
39-
40-
return App;
41-
},
4224
});
4325

4426
app.registerPlugin({

admin/src/translations/en.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
2-
"timezone-select.plugin.name": "timezone",
32
"timezone-select.label": "Time zone",
4-
"timezone-select.description": "Select any time zone",
5-
"timezone-select.unsupported-timezone": "Unsupported time zone \"{timezone}\"",
6-
"timezone-select.default-value-description": "Has to follow the time zone designations form the IANA Time Zone Database format i.e. America/New_York or Europe/London."
3+
"timezone-select.placeholder": "Select a time zone or start typing the name of a city",
4+
"timezone-select.aria-label": "Time zone",
5+
"timezone-select.description": "Select a time zone",
6+
"plugin.description.long": "A Strapi custom field to select a Olson/IANA time zone (e.g. Europe/Paris, America/New_York, UTC, Etc/GMT+8).",
7+
"plugin.description.short": "A Strapi custom field to select a time zone.",
8+
"plugin.name": "Timezone select"
79
}

admin/src/translations/fr.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"timezone-select.label": "Fuseau horaire",
3+
"timezone-select.placeholder": "Selectionner un fuseau horaire ou commencer à taper le nom d'une ville",
4+
"timezone-select.aria-label": "Fuseau horaire",
5+
"timezone-select.description": "Selectionner un fuseau horaire",
6+
"plugin.description.long": "Un champ custom Strapi pour selectionner un fuseau horaire Olson/IANA (ex. Europe/Paris, America/New_York, UTC, Etc/GMT+8).",
7+
"plugin.description.short": "Un champ custom Strapi pour selectionner un fuseau horaire.",
8+
"plugin.name": "Timezone select"
9+
}

0 commit comments

Comments
 (0)