|
@@ -0,0 +1,218 @@
|
|
|
+<Header>Gql</Header>
|
|
|
+
|
|
|
+<body>
|
|
|
+ <script>
|
|
|
+ function jwtDecode(token) { // расщифровки токена авторизации
|
|
|
+ if (!token || typeof token != "string")
|
|
|
+ return undefined;
|
|
|
+ let tokenArr = token.split(".");
|
|
|
+ if (tokenArr.length != 3)
|
|
|
+ return undefined;
|
|
|
+ try {
|
|
|
+ let tokenJsonStr = atob(tokenArr[1]);
|
|
|
+ let tokenJson = JSON.parse(tokenJsonStr);
|
|
|
+ return tokenJson;
|
|
|
+ }
|
|
|
+ catch {
|
|
|
+ return undefined;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ function combineReducers(reducers) {
|
|
|
+ function totalReducer(totalState = {}, action) {
|
|
|
+ const newTotalState = {} //объект, который будет хранить только новые состояния дочерних редьюсеров
|
|
|
+
|
|
|
+ //цикл + квадратные скобочки позволяют написать код, который будет работать с любыми количеством дочерных редьюсеров
|
|
|
+ for (const [reducerName, childReducer] of Object.entries(reducers)) {
|
|
|
+ const newState = childReducer(totalState[reducerName], action) //запуск дочернего редьюсера
|
|
|
+ if (newState !== totalState[reducerName]) { //если он отреагировал на action
|
|
|
+ newTotalState[reducerName] = newState //добавляем его в newTotalState
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ //Универсальная проверка на то, что хотя бы один дочерний редьюсер создал новый стейт:
|
|
|
+ if (Object.values(newTotalState).length) {
|
|
|
+ return { ...totalState, ...newTotalState } //создаем новый общий стейт, накладывая новый стейты дочерних редьюсеров на старые
|
|
|
+ }
|
|
|
+
|
|
|
+ return totalState //если экшен не был понят ни одним из дочерних редьюсеров, возвращаем общий стейт как был.
|
|
|
+ }
|
|
|
+
|
|
|
+ return totalReducer
|
|
|
+ }
|
|
|
+ function promiseReducer(state = {}, action) { // диспетчер обработки
|
|
|
+ if (action) {
|
|
|
+ if (action.type === 'PROMISE') {
|
|
|
+ let newState = { ...state };
|
|
|
+ newState[action.name] = { status: action.status, payload: action.payload, error: action.error };
|
|
|
+ return newState;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return state;
|
|
|
+ }
|
|
|
+ function authReducer(state = {}, action) { // диспетчер обработки login
|
|
|
+ if (action) {
|
|
|
+ if (action.type === 'AUTH_LOGIN') {
|
|
|
+ let newState = { ...state };
|
|
|
+ newState.token = action.token;
|
|
|
+ newState.payload = jwtDecode(action.token);
|
|
|
+ if (!newState.payload) {
|
|
|
+ newState.token = undefined;
|
|
|
+ }
|
|
|
+ return newState;
|
|
|
+ }
|
|
|
+ else if (action.type === 'AUTH_LOGOUT') {
|
|
|
+ let newState = { ...state };
|
|
|
+ newState.token = undefined;
|
|
|
+ newState.payload = undefined;
|
|
|
+ return newState;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return state;
|
|
|
+ }
|
|
|
+ function createStore(reducer) {
|
|
|
+ let state = reducer(undefined, {}) //стартовая инициализация состояния, запуск редьюсера со state === undefined
|
|
|
+ let cbs = [] //массив подписчиков
|
|
|
+
|
|
|
+ const getState = () => state //функция, возвращающая переменную из замыкания
|
|
|
+ const subscribe = cb => (cbs.push(cb), //запоминаем подписчиков в массиве
|
|
|
+ () => cbs = cbs.filter(c => c !== cb)) //возвращаем функцию unsubscribe, которая удаляет подписчика из списка
|
|
|
+
|
|
|
+ function dispatch(action) {
|
|
|
+ if (typeof action === 'function') { //если action - не объект, а функция
|
|
|
+ return action(dispatch, getState) //запускаем эту функцию и даем ей dispatch и getState для работы
|
|
|
+ }
|
|
|
+ const newState = reducer(state, action) //пробуем запустить редьюсер
|
|
|
+ if (newState !== state) { //проверяем, смог ли редьюсер обработать action
|
|
|
+ state = newState //если смог, то обновляем state
|
|
|
+ for (let cb of cbs) cb() //и запускаем подписчиков
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ getState, //добавление функции getState в результирующий объект
|
|
|
+ dispatch,
|
|
|
+ subscribe //добавление subscribe в объект
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ function gql(url, query, vars) {
|
|
|
+ let fetchSettings =
|
|
|
+ {
|
|
|
+ method: "POST",
|
|
|
+ headers:
|
|
|
+ {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ "Accept": "application/json"
|
|
|
+ },
|
|
|
+ body: JSON.stringify(
|
|
|
+ {
|
|
|
+ query: query,
|
|
|
+ variables: vars
|
|
|
+
|
|
|
+ })
|
|
|
+ };
|
|
|
+ return fetch(url, fetchSettings).then(res => res.json());
|
|
|
+ }
|
|
|
+
|
|
|
+ function actionPromiseGql(name, promise) {
|
|
|
+ return async function Exec(dispatch) {
|
|
|
+ dispatch(actionPending(name)) //сигнализируем redux, что промис начался
|
|
|
+ try {
|
|
|
+ const payload = await promise; //ожидаем промиса;
|
|
|
+ let result = Object.values(payload.data)[0];
|
|
|
+ dispatch(actionFulfilled(name, result)); //сигнализируем redux, что промис успешно выполнен
|
|
|
+ return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ dispatch(actionRejected(name, error)) //в случае ошибки - сигнализируем redux, что промис несложился
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ function actionAuthGql(promise) {
|
|
|
+ return async function Exec(dispatch) {
|
|
|
+ try {
|
|
|
+ const payload = await promise //ожидаем промиса;
|
|
|
+ let result = Object.values(payload.data)[0];
|
|
|
+ dispatch(actionAuthLogin(result)); //сигнализируем redux, что промис успешно выполнен
|
|
|
+ return result; //в месте запуска store.dispatch с этим thunk можно так же получить результат промиса
|
|
|
+ }
|
|
|
+ catch (error) {
|
|
|
+ dispatch(actionLogOut()) //в случае ошибки - сигнализируем redux, что промис несложился
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+ const actionPending = (name) => ({ type: 'PROMISE', name: name, status: 'PENDING' });
|
|
|
+ const actionFulfilled = (name, payload) => ({ type: 'PROMISE', name: name, payload: payload, status: 'FULFILLED' });
|
|
|
+ const actionRejected = (name, error) => ({ type: 'PROMISE', name: name, error: error, status: 'REJECTED' });
|
|
|
+ const actionAuthLogin = token => ({ type: 'AUTH_LOGIN', token });
|
|
|
+ const actionAuthLogout = () => ({ type: 'AUTH_LOGOUT' });
|
|
|
+
|
|
|
+ const gqlRootCats = () => {
|
|
|
+ const catQuery = `query roots {
|
|
|
+ CategoryFind(query: "[{\\"parent\\": null }]") {
|
|
|
+ _id name
|
|
|
+ }}`;
|
|
|
+ return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery);
|
|
|
+ }
|
|
|
+ const actionRootCats = () =>
|
|
|
+ actionPromiseGql('rootCats', gqlRootCats());
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ ///////////////////////////////////////
|
|
|
+ const gqlCategoryFindOne = (id) => {
|
|
|
+ const catQuery = `query CategoryFindOne($q: String) {
|
|
|
+ CategoryFindOne(query: $q) {
|
|
|
+ _id name
|
|
|
+ parent { _id name }
|
|
|
+ subCategories { _id name }
|
|
|
+ goods { _id name price description
|
|
|
+ images { url }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }`;
|
|
|
+ return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
|
|
|
+ }
|
|
|
+ const actionCategoryFindOne = (id) =>
|
|
|
+ actionPromiseGql('catFindOne', gqlCategoryFindOne(id));
|
|
|
+ /////////////////////////////////////////
|
|
|
+ const gqlGoodFindOne = (id) => {
|
|
|
+ const catQuery = `
|
|
|
+ query GoodFindOne($q: String) {
|
|
|
+ GoodFindOne(query: $q) {
|
|
|
+ _id name price description
|
|
|
+ images {
|
|
|
+ url
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ `;
|
|
|
+ return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", catQuery, { q: `[{\"_id\": \"${id}\"}]` });
|
|
|
+ }
|
|
|
+ const actionGoodFindOne = (id) =>
|
|
|
+ actionPromiseGql('goodsFindOne', gqlGoodFindOne(id));
|
|
|
+ //////////////////////////////////
|
|
|
+ const gqlAuthLogin = (login, password) => {
|
|
|
+ const loginQuery = `query login($login:String, $password:String){
|
|
|
+ login(login:$login, password:$password)
|
|
|
+ }`;
|
|
|
+
|
|
|
+ return gql("http://shop-roles.node.ed.asmer.org.ua/graphql", loginQuery, { login: login, password: password });
|
|
|
+ }
|
|
|
+ const actionAuthLoginFunc = (login, password) =>
|
|
|
+ actionAuthGql(gqlAuthLogin(login, password));
|
|
|
+ ////////////////////////////////////////
|
|
|
+ const store = createStore(combineReducers({ promiseReducer, authReducer }));
|
|
|
+
|
|
|
+ store.subscribe(() => {
|
|
|
+ console.log(store.getState())
|
|
|
+ });
|
|
|
+ //store.dispatch(actionRootCats());
|
|
|
+ //store.dispatch(actionCategoryFindOne("6262ca7dbf8b206433f5b3d1"));
|
|
|
+ //store.dispatch(actionGoodFindOne("62d3099ab74e1f5f2ec1a125"));
|
|
|
+ store.dispatch(actionAuthLoginFunc("Berg", "123456789"));
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ </script>
|
|
|
+</body>
|