diff --git a/src/pages/home/model.ts b/src/pages/home/model.ts index 523fbbe..c0e12f8 100644 --- a/src/pages/home/model.ts +++ b/src/pages/home/model.ts @@ -1,5 +1,5 @@ import { attach, createEvent, createStore, sample } from 'effector'; -import { not } from 'patronum'; +import { not, spread } from 'patronum'; import { historyPush } from 'features/navigation'; import { $session, checkAuthenticated } from 'features/session'; @@ -7,6 +7,8 @@ import { $session, checkAuthenticated } from 'features/session'; import * as api from 'shared/api'; import { createStart } from 'shared/lib/page-routing'; +import { Application } from './types'; + export const pageStarted = createStart(); export const logoutClicked = createEvent(); @@ -14,12 +16,29 @@ export const logoutClicked = createEvent(); export const $fullName = createStore(''); export const $email = createStore(''); export const $showError = createStore(false); +export const $applicationsInstalled = createStore([]); +export const $applicationsAvailable = createStore([]); const sessionDeleteFx = attach({ effect: api.sessionDelete }); +const applicationsListFx = attach({ effect: api.applicationsList }); const pageReady = checkAuthenticated({ when: pageStarted }); $showError.reset(pageReady); +sample({ + clock: pageReady, + fn: () => ({ body: {} }), + target: applicationsListFx, +}); + +spread({ + source: applicationsListFx.doneData.map((response) => response.answer), + targets: { + available: $applicationsAvailable, + installed: $applicationsInstalled, + }, +}); + sample({ clock: $session, fn: (session) => `${session?.firstName} ${session?.lastName}`, @@ -46,3 +65,4 @@ sample({ }); $showError.on(sessionDeleteFx.fail, () => true); +$showError.on(applicationsListFx.fail, () => true); diff --git a/src/pages/home/page.tsx b/src/pages/home/page.tsx index a2b16e0..f8af0b8 100644 --- a/src/pages/home/page.tsx +++ b/src/pages/home/page.tsx @@ -1,17 +1,21 @@ import { reflect, variant } from '@effector/reflect/ssr'; import { createEvent, createStore } from 'effector'; -import { useStore } from 'effector-react/scope'; +import { useList, useStore } from 'effector-react/scope'; import React from 'react'; import { AccessoCard, ButtonPrimary, FailureText } from 'shared/design'; import { CenterCardTemplate } from 'shared/ui'; +import { Application } from './types'; + //#region Ports export const logoutClicked = createEvent>(); export const $fullName = createStore(''); export const $email = createStore(''); export const $showError = createStore(false); +export const $applicationsInstalled = createStore([]); +export const $applicationsAvailable = createStore([]); //#endregion export const HomePage = () => { @@ -28,11 +32,39 @@ export const HomePage = () => { Log out + + ); }; +function InstalledApplications() { + const installed = useStore($applicationsInstalled); + if (installed.length === 0) return null; + + return ( + + {installed.map((application) => ( +
{application.title}
+ ))} +
+ ); +} + +function AvailableApplications() { + const available = useStore($applicationsAvailable); + if (available.length === 0) return null; + + return ( + + {available.map((application) => ( +
{application.title}
+ ))} +
+ ); +} + const ErrorBlock = variant({ source: $showError.map(String), cases: { @@ -50,3 +82,18 @@ const LogoutButton = reflect({ onClick: logoutClicked, }, }); + +function ApplicationsSection({ + title, + children, +}: { + title: string; + children: React.ReactNode | null; +}) { + return ( +
+

{title}

+
{children}
+
+ ); +} diff --git a/src/pages/home/types.ts b/src/pages/home/types.ts new file mode 100644 index 0000000..7ad7e86 --- /dev/null +++ b/src/pages/home/types.ts @@ -0,0 +1,3 @@ +import { ApplicationsListDone } from 'shared/api'; + +export type Application = ApplicationsListDone['answer']['installed'][number]; diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts index d2df538..e9312aa 100644 --- a/src/shared/api/index.ts +++ b/src/shared/api/index.ts @@ -2,8 +2,8 @@ // --- // This file is automatically generated by openapi with preset effector-openapi-preset // Do not edit this file directly. Instead open openapi config file and follow the link in "file" -import { createEffect } from 'effector'; import * as typed from 'typed-contracts'; +import { createEffect } from 'effector'; import { requestFx } from './request'; @@ -64,7 +64,7 @@ function parseByStatus< //#endregion prebuilt code/* --- */ //#region oauthAuthorizeRequest -type OauthAuthorizeRequest = { +export type OauthAuthorizeRequest = { body: { /* responseType is set to code indicating that you want an authorization code as the response. */ responseType: 'code'; @@ -180,7 +180,7 @@ export const oauthAuthorizeRequest = createEffect< /* --- */ //#region accessRecoverySendEmail -type AccessRecoverySendEmail = { +export type AccessRecoverySendEmail = { body: { email: string; }; @@ -235,7 +235,7 @@ export const accessRecoverySendEmail = createEffect< /* --- */ //#region accessRecoverySetPassword -type AccessRecoverySetPassword = { +export type AccessRecoverySetPassword = { body: { password: string; code: string; @@ -289,7 +289,7 @@ export const accessRecoverySetPassword = createEffect< /* --- */ //#region registerRequest -type RegisterRequest = { +export type RegisterRequest = { body: { email: string; }; @@ -347,7 +347,7 @@ export const registerRequest = createEffect< /* --- */ //#region registerConfirmation -type RegisterConfirmation = { +export type RegisterConfirmation = { body: { confirmationCode: string; firstName: string; @@ -410,7 +410,7 @@ export const registerConfirmation = createEffect< /* --- */ //#region sessionCreate -type SessionCreate = { +export type SessionCreate = { body: { email: string; password: string; @@ -465,7 +465,7 @@ export const sessionCreate = createEffect; +}; + +/* Something went wrong */ +export const applicationsListInternalServerError = typed.nul; +export type ApplicationsListFail = + | { + status: 'internal_server_error'; + error: typed.Get; + } + | GenericErrors; + +/* List available and installed applications for the user */ +export const applicationsList = createEffect< + ApplicationsList, + ApplicationsListDone, + ApplicationsListFail +>({ + async handler() { + const name = 'applicationsList.body'; + const response = await requestFx({ + path: '/applications.list', + method: 'POST', + }); + return parseByStatus(name, response, { + 200: ['ok', applicationsListOk], + 500: ['internal_server_error', applicationsListInternalServerError], + }); + }, +}); +//#endregion applicationsList