-
Notifications
You must be signed in to change notification settings - Fork 28
Todoist - MVP widget #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,8 @@ | |
"redux-persist": "6.0.0", | ||
"redux-saga": "1.1.3", | ||
"redux-sagas-injector": "1.1.1", | ||
"selectorator": "4.0.3" | ||
"selectorator": "4.0.3", | ||
"todoist-rest-api": "^1.3.4" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A second, more generic comment: I've started the project without any server at all, thinking I could go as far as possible with that approach. However, I've now implemented the first 3rd-party-data-driven widgets (Cryptocurrencies, GitHub Stats). Now I think we should handle all 3rd-party data via the server module for the following reasons:
In other words, I would move this module from The server module is only a few weeks old, but there are already 3 different routes implemented (2 of them actually used) that you can check as an example. |
||
}, | ||
"devDependencies": { | ||
"@storybook/addon-a11y": "5.3.18", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,7 @@ const Drawer: React.FC<Props> = ({ addWidgetToLayout }) => { | |
<div className="p-2 text-center uppercase font-bold text-2"> | ||
{t(`widget.category.${category}`)} | ||
</div> | ||
{categoriesWithWidgets[category].map( | ||
{categoriesWithWidgets[category] ? categoriesWithWidgets[category].map( | ||
({ widgetType: widget }: WidgetProperties) => ( | ||
<div key={widget} className="flex justify-between py-2"> | ||
{t(`widget.${widget}.name`)} | ||
|
@@ -36,7 +36,9 @@ const Drawer: React.FC<Props> = ({ addWidgetToLayout }) => { | |
</Button> | ||
</div> | ||
) | ||
)} | ||
) : | ||
<div className="flex justify-between py-2 italic">{t("widget.common.noWidget")}</div> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we shouldn't use a category that has no widgets (that's why the unused are ones commented out). This way, this change (and the according label) are unnecessary. That said, the widget drawer requires a major redesign anyway. |
||
} | ||
</div> | ||
))} | ||
</div> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -75,6 +75,15 @@ export default { | |
initialOptions: {}, | ||
initialMeta: {} | ||
}, | ||
todoist: { | ||
configurable: true, | ||
widgetType: "todoist", | ||
category: "productivity", | ||
initialHeight: 3, | ||
initialWidth: 4, | ||
initialOptions: {}, | ||
initialMeta: { updateCycle: { hours: 24 } } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is autogenerated. Please run |
||
}, | ||
"totd-chemical-elements": { | ||
configurable: false, | ||
widgetType: "totd-chemical-elements", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import React from "react"; | ||
import { storiesOf } from "@storybook/react"; | ||
|
||
import { connectedWidgetProps } from "common/utils/mock"; | ||
import { Widget } from "components/widget"; | ||
|
||
const Story = () => { | ||
return ( | ||
<Widget | ||
{...connectedWidgetProps} | ||
id="todoist-mock-id" | ||
type="todoist" | ||
data={{ | ||
token: "xxxx" | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
storiesOf("Widgets.Todoist", module).add("Variants", () => <Story />); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import React from "react"; | ||
import { shallow, ShallowWrapper } from "enzyme"; | ||
|
||
import { widgetProps } from "common/utils/mock"; | ||
|
||
import Todoist from "../index"; | ||
|
||
describe("<Todoist />", () => { | ||
let wrapper: ShallowWrapper; | ||
|
||
beforeEach(() => { | ||
wrapper = shallow( | ||
<Todoist | ||
{...widgetProps} | ||
id="todoist-mock-id" | ||
token="xxxx" | ||
/> | ||
); | ||
}); | ||
|
||
it("renders without error", () => { | ||
expect(wrapper.isEmptyRender()).toBe(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FYI - the default (= generated) tests are currently very basic, because I'm planning to replace |
||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import React from "react"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
import { ConfigurationProps } from "widgets/index"; | ||
import Input from "components/forms/input"; | ||
|
||
const Configuration = ({ | ||
id, | ||
options, | ||
setOptions, | ||
save | ||
}: ConfigurationProps) => { | ||
const { t } = useTranslation(); | ||
return ( | ||
<> | ||
<Input | ||
label={t("widget.todoist.configuration.token")} | ||
className="mb-6" | ||
value={options.token} | ||
setValue={value => setOptions({ token: value })} | ||
></Input> | ||
</> | ||
); | ||
}; | ||
|
||
export default Configuration; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import React, { memo } from "react"; | ||
|
||
import useTriggerUpdate from "common/hooks/useTriggerUpdate"; | ||
|
||
import { WidgetProps } from "../index"; | ||
|
||
export { saga } from "./sagas"; | ||
|
||
const Todoist: React.FC<Props> = memo( | ||
({ | ||
id, | ||
token, | ||
meta, | ||
setData, | ||
triggerUpdate | ||
}) => { | ||
useTriggerUpdate({ id, params: { token }, meta, triggerUpdate }, [ | ||
token | ||
]); | ||
|
||
return ( | ||
<div className="flex flex-col items-center text-center"> | ||
<div className="text-4 font-semibold"> | ||
<span className="center"> Todoist </span> | ||
<span className="text-2 uppercase">{token}</span> | ||
</div> | ||
</div> | ||
); | ||
} | ||
); | ||
|
||
interface Props extends WidgetProps { | ||
token: string; | ||
} | ||
|
||
export default Todoist; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import WidgetCategory from "../categories"; | ||
|
||
export const widgetType = "todoist"; | ||
export const category = WidgetCategory.Productivity; | ||
export const initialHeight = 2; | ||
export const initialWidth = 3; | ||
export const initialOptions = { | ||
token: "xxxxx" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
}; | ||
export const initialMeta = { | ||
updateCycle: { minutes: 15 } | ||
}; |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,42 @@ | ||||||||
import { put, call, takeEvery } from "@redux-saga/core/effects"; | ||||||||
import { PayloadAction } from "@reduxjs/toolkit"; | ||||||||
|
||||||||
import log from "common/log"; | ||||||||
import api, { CRYPTOCURRENCIES_PRICE } from "common/api"; | ||||||||
import { | ||||||||
setData, | ||||||||
triggerUpdate, | ||||||||
updatePending, | ||||||||
updateError, | ||||||||
updateSuccess, | ||||||||
TriggerUpdateAction | ||||||||
} from "components/widget/duck"; | ||||||||
|
||||||||
import { widgetType } from "./properties"; | ||||||||
|
||||||||
const fetchCryptocurrencyPrice = async (params: { [key: string]: any }) => { | ||||||||
const response = await api.get(CRYPTOCURRENCIES_PRICE, { params }); | ||||||||
return response.data; | ||||||||
}; | ||||||||
|
||||||||
function* onTriggerUpdate(action: PayloadAction<TriggerUpdateAction>) { | ||||||||
const { id, params } = action.payload; | ||||||||
yield put(updatePending(id)); | ||||||||
try { | ||||||||
const data = yield call(fetchCryptocurrencyPrice, params); | ||||||||
yield put( | ||||||||
setData({ | ||||||||
id, | ||||||||
values: data | ||||||||
}) | ||||||||
); | ||||||||
yield put(updateSuccess(id)); | ||||||||
} catch (error) { | ||||||||
log.error(error); | ||||||||
yield put(updateError(id)); | ||||||||
Comment on lines
+35
to
+36
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please merge/rebase latest
Suggested change
|
||||||||
} | ||||||||
} | ||||||||
|
||||||||
export function* saga() { | ||||||||
yield takeEvery(triggerUpdate(widgetType).toString(), onTriggerUpdate); | ||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use exact package versions to make sure that all instances (development, production, CI) use exactly the same versions:
With yarn, you can enforce this with
yarn add package-name --exact
(or-E
), with npm it'snpm install package-name --save --save-exact
.