Compare commits
2 commits
94b95e884e
...
e8efe9859b
Author | SHA1 | Date | |
---|---|---|---|
e8efe9859b | |||
cef6fd45a9 |
23 changed files with 235 additions and 310 deletions
|
@ -10,5 +10,5 @@ export const FetchLoader: FC<FetchLoaderProps> = ({ children, active, placeholde
|
||||||
if (!active) {
|
if (!active) {
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
return <div className="animate-pulse flex-1 py-1">{placeholder ?? null}</div>;
|
return <div className="animate-pulse flex-1 py-1">{placeholder}</div>;
|
||||||
};
|
};
|
||||||
|
|
|
@ -20,7 +20,7 @@ export type BaseQueryFetchParams<Body> = {
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BaseQueryWithReauthParams = {
|
export type BaseQueryWithRefreshParams = {
|
||||||
authentication: {
|
authentication: {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
|
@ -70,11 +70,11 @@ export const baseQuery =
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const baseQueryWithReauth =
|
export const baseQueryWithRefresh =
|
||||||
<Response = { data: unknown }, RequestBody = any>({
|
<Response = { data: unknown }, RequestBody = any>({
|
||||||
baseUrl,
|
baseUrl,
|
||||||
authentication,
|
authentication,
|
||||||
}: BaseQueryWithReauthParams): BaseQueryFn<BaseQueryFetchParams<RequestBody>, Response, FetchBaseQueryError> =>
|
}: BaseQueryWithRefreshParams): BaseQueryFn<BaseQueryFetchParams<RequestBody>, Response, FetchBaseQueryError> =>
|
||||||
async (args, api, extraOptions) => {
|
async (args, api, extraOptions) => {
|
||||||
const authReducer: any = (api.getState() as AuthRootState).procyon[AUTH_REDUCER_NAME];
|
const authReducer: any = (api.getState() as AuthRootState).procyon[AUTH_REDUCER_NAME];
|
||||||
const accessTokenExpired = new Date(authReducer.accessExpiresTime) < new Date();
|
const accessTokenExpired = new Date(authReducer.accessExpiresTime) < new Date();
|
||||||
|
|
38
packages/auth/src/hook/useAuthCode.ts
Normal file
38
packages/auth/src/hook/useAuthCode.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useLocation } from 'wouter';
|
||||||
|
|
||||||
|
import { setAuthTokens } from '@procyon/auth/slice';
|
||||||
|
|
||||||
|
import { fetchTokens } from '../actions';
|
||||||
|
import { UseAuthParams } from '../types';
|
||||||
|
|
||||||
|
export const useAuthCode = ({ baseUrl, clientId, tokenEndpoint, redirectUri }: UseAuthParams) => {
|
||||||
|
const dispatch = useDispatch<any>();
|
||||||
|
|
||||||
|
const [, setLocation] = useLocation();
|
||||||
|
|
||||||
|
return useCallback(async (code: string) => {
|
||||||
|
const { data: tokens } = await fetchTokens({
|
||||||
|
baseUrl,
|
||||||
|
tokenEndpoint,
|
||||||
|
clientId,
|
||||||
|
redirectUri: redirectUri,
|
||||||
|
grantType: 'authorization_code',
|
||||||
|
code,
|
||||||
|
});
|
||||||
|
if (tokens) {
|
||||||
|
dispatch(
|
||||||
|
setAuthTokens({
|
||||||
|
accessToken: tokens.access_token,
|
||||||
|
refreshToken: tokens.refresh_token,
|
||||||
|
idToken: tokens.id_token,
|
||||||
|
accessExpiresIn: tokens.expires_in,
|
||||||
|
refreshExpiresIn: tokens.refresh_expires_in,
|
||||||
|
sessionState: tokens.session_state,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setLocation('/');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
};
|
29
packages/auth/src/hook/useLogin.ts
Normal file
29
packages/auth/src/hook/useLogin.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { UseAuthParams } from '../types';
|
||||||
|
|
||||||
|
const generateState = () =>
|
||||||
|
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
const r = (Math.random() * 16) | 0,
|
||||||
|
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useLogin = ({ baseUrl, clientId, authEndpoint, redirectUri }: UseAuthParams) => {
|
||||||
|
return useCallback(() => {
|
||||||
|
const state = generateState();
|
||||||
|
const urlParams: Record<string, string> = {
|
||||||
|
client_id: clientId,
|
||||||
|
redirect_uri: redirectUri,
|
||||||
|
response_mode: 'fragment',
|
||||||
|
response_type: 'code',
|
||||||
|
scope: 'openid profile',
|
||||||
|
state,
|
||||||
|
};
|
||||||
|
const connectionURI = new URL(`${baseUrl}${authEndpoint}`);
|
||||||
|
for (const key of Object.keys(urlParams)) {
|
||||||
|
connectionURI.searchParams.append(key, urlParams[key]);
|
||||||
|
}
|
||||||
|
window.location.assign(connectionURI);
|
||||||
|
}, []);
|
||||||
|
};
|
|
@ -1,72 +0,0 @@
|
||||||
import { useCallback } from 'react';
|
|
||||||
import { useDispatch } from 'react-redux';
|
|
||||||
import { useLocation } from 'wouter';
|
|
||||||
|
|
||||||
import { setAuthTokens } from '@procyon/auth/slice';
|
|
||||||
|
|
||||||
import { fetchTokens } from '../actions';
|
|
||||||
import { UseOAuthParams } from '../types';
|
|
||||||
|
|
||||||
const generateState = () =>
|
|
||||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
|
||||||
const r = (Math.random() * 16) | 0,
|
|
||||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
|
||||||
return v.toString(16);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* test
|
|
||||||
* @param param0 test
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export const useOAuth = ({ baseUrl, clientId, authEndpoint, tokenEndpoint, redirectUri }: UseOAuthParams) => {
|
|
||||||
const dispatch = useDispatch<any>();
|
|
||||||
|
|
||||||
const [, setLocation] = useLocation();
|
|
||||||
|
|
||||||
const redirect = useCallback(() => {
|
|
||||||
const state = generateState();
|
|
||||||
const urlParams: Record<string, string> = {
|
|
||||||
client_id: clientId,
|
|
||||||
redirect_uri: redirectUri,
|
|
||||||
response_mode: 'fragment',
|
|
||||||
response_type: 'code',
|
|
||||||
scope: 'openid profile',
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
const conectionURI = new URL(`${baseUrl}${authEndpoint}`);
|
|
||||||
for (const key of Object.keys(urlParams)) {
|
|
||||||
conectionURI.searchParams.append(key, urlParams[key]);
|
|
||||||
}
|
|
||||||
window.location.assign(conectionURI);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const obtainTokens = useCallback(async (code: string) => {
|
|
||||||
const { data: tokens } = await fetchTokens({
|
|
||||||
baseUrl,
|
|
||||||
tokenEndpoint,
|
|
||||||
clientId,
|
|
||||||
redirectUri: redirectUri,
|
|
||||||
grantType: 'authorization_code',
|
|
||||||
code,
|
|
||||||
});
|
|
||||||
if (tokens) {
|
|
||||||
dispatch(
|
|
||||||
setAuthTokens({
|
|
||||||
accessToken: tokens.access_token,
|
|
||||||
refreshToken: tokens.refresh_token,
|
|
||||||
idToken: tokens.id_token,
|
|
||||||
accessExpiresIn: tokens.expires_in,
|
|
||||||
refreshExpiresIn: tokens.refresh_expires_in,
|
|
||||||
sessionState: tokens.session_state,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setLocation('/');
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
|
||||||
redirect,
|
|
||||||
obtainTokens,
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||||
import { addSeconds } from 'date-fns';
|
import { addSeconds } from 'date-fns';
|
||||||
|
|
||||||
import { AUTH_REDUCER_NAME, AuthAttributes, AuthRootState, AuthTokens, ModifyAuthAttribute } from './types';
|
import { AUTH_REDUCER_NAME, AuthAttributes, AuthRootState, AuthTokenPayload, ModifyAuthAttribute } from './types';
|
||||||
|
|
||||||
const initialState: AuthAttributes = {
|
const initialState: AuthAttributes = {
|
||||||
authenticated: undefined,
|
authenticated: undefined,
|
||||||
|
@ -19,7 +19,7 @@ const authSlice = createSlice({
|
||||||
name: AUTH_REDUCER_NAME,
|
name: AUTH_REDUCER_NAME,
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setAuthTokens: (_, action: PayloadAction<AuthTokens>) => {
|
setAuthTokens: (_, action: PayloadAction<AuthTokenPayload>) => {
|
||||||
const { accessExpiresIn, refreshExpiresIn } = action.payload;
|
const { accessExpiresIn, refreshExpiresIn } = action.payload;
|
||||||
return {
|
return {
|
||||||
...action.payload,
|
...action.payload,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { ROOT_REDUCER_NAME } from '@procyon/constants/redux';
|
||||||
|
|
||||||
export const AUTH_REDUCER_NAME = 'auth';
|
export const AUTH_REDUCER_NAME = 'auth';
|
||||||
|
|
||||||
export type AuthTokens = {
|
export type AuthTokenPayload = {
|
||||||
accessExpiresIn?: number;
|
accessExpiresIn?: number;
|
||||||
accessToken?: string;
|
accessToken?: string;
|
||||||
idToken?: string;
|
idToken?: string;
|
||||||
|
@ -16,7 +16,7 @@ export type AuthAttributes<CustomAttributes = unknown> = {
|
||||||
authenticated?: boolean;
|
authenticated?: boolean;
|
||||||
refreshExpiresTime?: string;
|
refreshExpiresTime?: string;
|
||||||
} & CustomAttributes &
|
} & CustomAttributes &
|
||||||
AuthTokens;
|
AuthTokenPayload;
|
||||||
|
|
||||||
export type ModifyAuthAttribute<P = unknown> = {
|
export type ModifyAuthAttribute<P = unknown> = {
|
||||||
key: keyof AuthAttributes<P>;
|
key: keyof AuthAttributes<P>;
|
||||||
|
@ -29,7 +29,7 @@ export type AuthRootState = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseOAuthParams = {
|
export type UseAuthParams = {
|
||||||
authEndpoint: string;
|
authEndpoint: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
clientId: string;
|
clientId: string;
|
||||||
|
@ -38,7 +38,7 @@ export type UseOAuthParams = {
|
||||||
tokenEndpoint: string;
|
tokenEndpoint: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FetchTokensParams = Pick<UseOAuthParams, 'baseUrl' | 'tokenEndpoint' | 'clientId'> & {
|
export type FetchTokensParams = Pick<UseAuthParams, 'baseUrl' | 'tokenEndpoint' | 'clientId'> & {
|
||||||
code: string;
|
code: string;
|
||||||
grantType: 'authorization_code';
|
grantType: 'authorization_code';
|
||||||
redirectUri: string;
|
redirectUri: string;
|
||||||
|
|
|
@ -2,9 +2,10 @@ import React, { FC, Fragment, useEffect, useMemo, useState } from 'react';
|
||||||
import { range } from 'ramda';
|
import { range } from 'ramda';
|
||||||
import { add, endOfMonth, endOfWeek, getWeeksInMonth, startOfMonth, startOfWeek, sub } from 'date-fns';
|
import { add, endOfMonth, endOfWeek, getWeeksInMonth, startOfMonth, startOfWeek, sub } from 'date-fns';
|
||||||
|
|
||||||
|
import { SizeEnum } from '@procyon/types/common';
|
||||||
import { isNilOrEmpty } from '@procyon/utils';
|
import { isNilOrEmpty } from '@procyon/utils';
|
||||||
|
|
||||||
import { Title, TitleSizeEnum } from '../../Title';
|
import { Text } from '../../Text';
|
||||||
import { CalendarCell, CalendarCellProps } from './CalendarCell';
|
import { CalendarCell, CalendarCellProps } from './CalendarCell';
|
||||||
import { CalendarDays, CalendarDaysProps } from './CalendarDays';
|
import { CalendarDays, CalendarDaysProps } from './CalendarDays';
|
||||||
import { Controller, ControllerProps } from './Controller';
|
import { Controller, ControllerProps } from './Controller';
|
||||||
|
@ -181,7 +182,7 @@ export function Calendar({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{!isNilOrEmpty(title) && <Title size={TitleSizeEnum.md}>{title}</Title>}
|
{!isNilOrEmpty(title) && <Text size={SizeEnum.md}>{title}</Text>}
|
||||||
<Control
|
<Control
|
||||||
layout={layout}
|
layout={layout}
|
||||||
onNextClick={handleClickNext}
|
onNextClick={handleClickNext}
|
||||||
|
|
|
@ -1,25 +1,14 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
import { DirectionEnum } from '@procyon/types/common';
|
|
||||||
|
|
||||||
export type SectionProps = {
|
export type SectionProps = {
|
||||||
Title: FC;
|
Title: FC;
|
||||||
children: any;
|
children: any;
|
||||||
space?: Partial<Record<DirectionEnum, number>>;
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Section: FC<SectionProps> = ({ Title, children, space = { [DirectionEnum.bottom]: 4 } }) => {
|
export const Section: FC<SectionProps> = ({ Title, children, className }) => (
|
||||||
const spaces: string[] = [];
|
<div className={className}>
|
||||||
for (const direct in space) {
|
|
||||||
spaces.push(`m${direct}-${space[direct]}`);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className={clsx(spaces)}>
|
|
||||||
{Title && <Title />}
|
{Title && <Title />}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
|
||||||
|
|
||||||
export default Section;
|
|
25
packages/components/src/Text.tsx
Normal file
25
packages/components/src/Text.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { FC, ReactElement } from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
|
import { SizeEnum } from '@procyon/types/common';
|
||||||
|
|
||||||
|
export type TextProps = {
|
||||||
|
children?: ReactElement | string;
|
||||||
|
className?: string;
|
||||||
|
size?: SizeEnum;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Text: FC<TextProps> = ({ size = SizeEnum.sm, children, className }) => (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
{
|
||||||
|
'text-base': size === SizeEnum.sm,
|
||||||
|
'text-2xl mb-1': size === SizeEnum.md,
|
||||||
|
'text-3xl mb-2': size === SizeEnum.lg,
|
||||||
|
'text-4xl mb-3': size === SizeEnum.xl,
|
||||||
|
},
|
||||||
|
className
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)
|
|
@ -1,39 +0,0 @@
|
||||||
import React, { FC, ReactElement } from 'react';
|
|
||||||
import clsx from 'clsx';
|
|
||||||
|
|
||||||
import { DirectionEnum, SizeEnum } from '@procyon/types/common';
|
|
||||||
|
|
||||||
export enum TitleSizeEnum {
|
|
||||||
lg = 'lg',
|
|
||||||
md = 'md',
|
|
||||||
xl = 'xl',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TitleProps = {
|
|
||||||
children?: ReactElement | string;
|
|
||||||
className?: string;
|
|
||||||
margin?: Partial<Record<DirectionEnum, number>>;
|
|
||||||
size?: TitleSizeEnum;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Title: FC<TitleProps> = ({ size = SizeEnum.sm, children, className, margin }) => {
|
|
||||||
const margins: string[] = [];
|
|
||||||
for (const direct in margin) {
|
|
||||||
margins.push(`m${direct}-${margin[direct]}`);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
{
|
|
||||||
'text-base': size === SizeEnum.sm,
|
|
||||||
'text-2xl mb-1': size === SizeEnum.md,
|
|
||||||
'text-3xl mb-2': size === SizeEnum.lg,
|
|
||||||
'text-4xl mb-3': size === SizeEnum.xl,
|
|
||||||
},
|
|
||||||
margins,
|
|
||||||
className
|
|
||||||
)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const EMPTY_OBJECT = {};
|
|
||||||
|
|
||||||
export const EMPTY_ARRAY: [] = [];
|
|
|
@ -43,13 +43,11 @@ module.exports = config;
|
||||||
|
|
||||||
<Unstyled>
|
<Unstyled>
|
||||||
<Message status={StatusEnum.info}>
|
<Message status={StatusEnum.info}>
|
||||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
If you want to use automatically extend access token, use `baseQueryWithRefresh` instead of
|
||||||
`baseQuery` from `@procyon/api/query`. Pass same parameters as to `useOauth` hook.
|
`baseQuery` from `@procyon/api/query`.
|
||||||
</Message>
|
</Message>
|
||||||
</Unstyled>
|
</Unstyled>
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
import { Canvas, Meta, Source, Unstyled } from '@storybook/blocks';
|
import { Canvas, Meta, Source } from '@storybook/blocks';
|
||||||
|
|
||||||
import Message from '@procyon/components/Message';
|
|
||||||
import { StatusEnum } from '@procyon/types/common';
|
|
||||||
|
|
||||||
import apiContent from './helpers/api?raw';
|
import apiContent from './helpers/api?raw';
|
||||||
import { FetchComponent } from './helpers/FetchComponent';
|
import { FetchComponent } from './helpers/FetchComponent';
|
||||||
|
@ -16,15 +13,6 @@ import reduxContent from './helpers/redux?raw';
|
||||||
|
|
||||||
`@procyon/api` module for fetch.
|
`@procyon/api` module for fetch.
|
||||||
|
|
||||||
<Unstyled>
|
|
||||||
<Message status={StatusEnum.info}>
|
|
||||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
|
||||||
`baseQuery` from `@procyon/api/query`. Pass same parameters as to `useOauth` hook.
|
|
||||||
</Message>
|
|
||||||
</Unstyled>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
### Define API via RTQuery
|
### Define API via RTQuery
|
||||||
|
|
|
@ -4,6 +4,7 @@ import Message from '@procyon/components/Message';
|
||||||
import { StatusEnum } from '@procyon/types/common';
|
import { StatusEnum } from '@procyon/types/common';
|
||||||
|
|
||||||
import storeCode from './helpers/redux?raw';
|
import storeCode from './helpers/redux?raw';
|
||||||
|
import hookCode from './helpers/hook?raw';
|
||||||
|
|
||||||
<Meta title="Auth/Introduction" />
|
<Meta title="Auth/Introduction" />
|
||||||
|
|
||||||
|
@ -11,44 +12,29 @@ import storeCode from './helpers/redux?raw';
|
||||||
|
|
||||||
`@procyon/auth` module for authenticated.
|
`@procyon/auth` module for authenticated.
|
||||||
|
|
||||||
For work with IdP (Identity Provider server) use `useOAuth` hook.
|
For work with IdP (Identity Provider) server use `useLogin` and `useAuthCode` hooks.
|
||||||
|
|
||||||
<Unstyled>
|
<Unstyled>
|
||||||
<Message status={StatusEnum.info}>
|
<Message status={StatusEnum.info}>
|
||||||
After reload page, reducer data are synchronized with browser session storage. So they are saved until close browser
|
When refresh page, data are synchronized with browser session storage.
|
||||||
tab.
|
|
||||||
</Message>
|
</Message>
|
||||||
</Unstyled>
|
</Unstyled>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<Unstyled>
|
<Unstyled>
|
||||||
<Message status={StatusEnum.error}>
|
<Message status={StatusEnum.info}>
|
||||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
If you want to use automatically extend access token, use `baseQueryWithRefresh` instead of
|
||||||
`baseQuery` from `@procyon/api/query` and pass same parameters as to `useOauth` hook.
|
`baseQuery` from `@procyon/api/query`.
|
||||||
</Message>
|
</Message>
|
||||||
</Unstyled>
|
</Unstyled>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
## Redux store
|
### Redux storage
|
||||||
|
|
||||||
Redux is used as storage. `@procyon/api` use this storage for pickup access token for fetch auth header.
|
|
||||||
|
|
||||||
<Unstyled>
|
|
||||||
<Message status={StatusEnum.warning}>
|
|
||||||
Is important to to use auth reducer under <b>procyon</b> main reducer.
|
|
||||||
</Message>
|
|
||||||
</Unstyled>
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
### example
|
|
||||||
|
|
||||||
<Source language="ts" code={storeCode} />
|
<Source language="ts" code={storeCode} />
|
||||||
|
|
||||||
### stored data
|
|
||||||
|
|
||||||
<PureArgsTable
|
<PureArgsTable
|
||||||
rows={{
|
rows={{
|
||||||
authenticated: {
|
authenticated: {
|
||||||
|
@ -86,3 +72,7 @@ Redux is used as storage. `@procyon/api` use this storage for pickup access toke
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
### Sample source
|
||||||
|
|
||||||
|
<Source language="ts" code={hookCode} />
|
|
@ -1,60 +0,0 @@
|
||||||
import { Meta, PureArgsTable, Source } from '@storybook/blocks';
|
|
||||||
|
|
||||||
import hookCode from './helpers/hook?raw';
|
|
||||||
|
|
||||||
<Meta title="Auth/useOAuth" />
|
|
||||||
|
|
||||||
# `useOAuth` hook
|
|
||||||
|
|
||||||
`import { useOAuth } from @procyon/auth/hook/useOAuth`
|
|
||||||
|
|
||||||
Hook will communicate with IdP server like a Keycloak.
|
|
||||||
|
|
||||||
<Source language="ts" code={hookCode} />
|
|
||||||
|
|
||||||
### Params
|
|
||||||
|
|
||||||
<PureArgsTable
|
|
||||||
rows={{
|
|
||||||
baseUrl: {
|
|
||||||
description: 'Base Identity Provider URL.',
|
|
||||||
name: 'baseUrl',
|
|
||||||
},
|
|
||||||
clientId: {
|
|
||||||
description: 'Client ID defined in IdP',
|
|
||||||
name: 'clientId',
|
|
||||||
},
|
|
||||||
authEndpoint: {
|
|
||||||
description: 'URI for redirect to IdP login form',
|
|
||||||
name: 'authEndpoint',
|
|
||||||
},
|
|
||||||
tokenEndpoint: {
|
|
||||||
description: 'Endpoint whitch is called for obtain tokends',
|
|
||||||
name: 'tokenEndpoint',
|
|
||||||
},
|
|
||||||
logoutEndpoint: {
|
|
||||||
description: 'Endpoint for revoke session',
|
|
||||||
name: 'logoutEndpoint',
|
|
||||||
},
|
|
||||||
redirectUrl: {
|
|
||||||
description: 'URL to redirect from IdP back to application',
|
|
||||||
name: 'redirectUrl',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
### Returns
|
|
||||||
|
|
||||||
<PureArgsTable
|
|
||||||
rows={{
|
|
||||||
redirect: {
|
|
||||||
description: 'Function. When call, make redirecto to IdP login page',
|
|
||||||
name: 'redirect',
|
|
||||||
},
|
|
||||||
obtainTokens: {
|
|
||||||
description:
|
|
||||||
'Function. When return from IdP, app will obtain auth code which allow to fetch access tokens. After obtain tokens, make redirect to root route (/).',
|
|
||||||
name: 'obtainTokens',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
|
@ -1,32 +1,36 @@
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useLocation } from 'wouter';
|
import { useLocation } from 'wouter';
|
||||||
|
|
||||||
import { useOAuth } from '@procyon/auth/hook/useOAuth';
|
import { useAuthCode } from '@procyon/auth/hook/useAuthCode';
|
||||||
|
import { useLogin } from '@procyon/auth/hook/useLogin';
|
||||||
|
|
||||||
|
const authConfig = {
|
||||||
|
baseUrl: 'http://keycloak.local',
|
||||||
|
clientId: 'client_id',
|
||||||
|
tokenEndpoint: '/realm/localdev/token',
|
||||||
|
authEndpoint: '/realm/localdev/auth',
|
||||||
|
redirectUri: 'http://localhost:3330/login/callback',
|
||||||
|
logoutEndpoint: '/realm/localdev/logout',
|
||||||
|
};
|
||||||
|
|
||||||
export const Component: FC = () => {
|
export const Component: FC = () => {
|
||||||
const [location] = useLocation();
|
const [location] = useLocation();
|
||||||
|
|
||||||
const { redirect, obtainTokens } = useOAuth({
|
const doLogin = useLogin(authConfig);
|
||||||
baseUrl: 'http://keycloak.local',
|
const changeCodeForTokens = useAuthCode(authConfig);
|
||||||
clientId: 'client_id',
|
|
||||||
tokenEndpoint: '/realm/localdev/token',
|
|
||||||
authEndpoint: '/realm/localdev/auth',
|
|
||||||
redirectUri: 'http://localhost:3330/login/callback',
|
|
||||||
logoutEndpoint: '/realm/localdev/logout',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (location === '/login/callback') {
|
if (location === '/login/callback') {
|
||||||
const params = new URLSearchParams(window.location.href);
|
const params = new URLSearchParams(window.location.href);
|
||||||
const code = params.get('code') ?? null;
|
const code = params.get('code') ?? null;
|
||||||
if (code) {
|
if (code) {
|
||||||
obtainTokens(code);
|
changeCodeForTokens(code);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button onClick={redirect}>login</button>
|
<button onClick={doLogin}>login</button>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
import Section from '@procyon/components/Section';
|
import { Section } from '@procyon/components/Section';
|
||||||
import { Title, TitleSizeEnum } from '@procyon/components/Title';
|
import { Text } from '@procyon/components/Text';
|
||||||
import { DirectionEnum } from '@procyon/types/common';
|
import { SizeEnum } from '@procyon/types/common';
|
||||||
|
|
||||||
type Story = StoryObj<typeof Section>;
|
type Story = StoryObj<typeof Section>;
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export default {
|
||||||
|
|
||||||
export const Default: Story = {
|
export const Default: Story = {
|
||||||
args: {
|
args: {
|
||||||
Title: () => <Title>Section</Title>,
|
Title: () => <Text>Section</Text>,
|
||||||
children: 'Content',
|
children: 'Content',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -34,24 +34,21 @@ export const WithoutTitle: Story = {
|
||||||
|
|
||||||
export const Medium: Story = {
|
export const Medium: Story = {
|
||||||
args: {
|
args: {
|
||||||
Title: () => <Title size={TitleSizeEnum.md}>Section</Title>,
|
Title: () => <Text size={SizeEnum.md}>Section</Text>,
|
||||||
children: 'content',
|
children: 'content',
|
||||||
space: { [DirectionEnum.y]: 2 },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Large: Story = {
|
export const Large: Story = {
|
||||||
args: {
|
args: {
|
||||||
Title: () => <Title size={TitleSizeEnum.lg}>Section</Title>,
|
Title: () => <Text size={SizeEnum.lg}>Section</Text>,
|
||||||
children: 'content',
|
children: 'content',
|
||||||
space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 3 },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExtraLarge: Story = {
|
export const ExtraLarge: Story = {
|
||||||
args: {
|
args: {
|
||||||
Title: () => <Title size={TitleSizeEnum.xl}>Section</Title>,
|
Title: () => <Text size={SizeEnum.xl}>Section</Text>,
|
||||||
children: 'content',
|
children: 'content',
|
||||||
space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 4 },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
61
stories/components/Text.stories.tsx
Normal file
61
stories/components/Text.stories.tsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
|
||||||
|
import { Text } from '@procyon/components/Text';
|
||||||
|
import { SizeEnum } from '@procyon/types/common';
|
||||||
|
|
||||||
|
type Story = StoryObj<typeof Text>;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: Text,
|
||||||
|
decorators: [
|
||||||
|
(Story) => (
|
||||||
|
<>
|
||||||
|
<Story />
|
||||||
|
content
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
],
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: "`import { Text } from '@procyon/components/Text';`",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tags: ['autodocs'],
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Default Text',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Standard: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Head title',
|
||||||
|
size: SizeEnum.sm,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Middle: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Head title',
|
||||||
|
size: SizeEnum.md,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Head title',
|
||||||
|
size: SizeEnum.lg,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExtraLarge: Story = {
|
||||||
|
args: {
|
||||||
|
children: 'Head title',
|
||||||
|
size: SizeEnum.xl,
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,48 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
|
|
||||||
import { Title, TitleSizeEnum } from '@procyon/components/Title';
|
|
||||||
import { DirectionEnum } from '@procyon/types/common';
|
|
||||||
|
|
||||||
type Story = StoryObj<typeof Title>;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
component: Title,
|
|
||||||
decorators: [
|
|
||||||
(Story) => (
|
|
||||||
<>
|
|
||||||
<Story />
|
|
||||||
content
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
],
|
|
||||||
parameters: {
|
|
||||||
docs: {
|
|
||||||
description: {
|
|
||||||
component: "`import { Title } from '@procyon/components/Title';`",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tags: ['autodocs'],
|
|
||||||
} as Meta;
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {
|
|
||||||
children: 'Default Title',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Header: Story = {
|
|
||||||
args: {
|
|
||||||
children: 'Head title',
|
|
||||||
size: TitleSizeEnum.md,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const HeaderWithSpace: Story = {
|
|
||||||
args: {
|
|
||||||
children: 'Head title',
|
|
||||||
size: TitleSizeEnum.md,
|
|
||||||
margin: { [DirectionEnum.bottom]: 4 },
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -49,7 +49,7 @@ export const Hook: Story = {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
showToaster({
|
showToaster({
|
||||||
id: 'toaster1',
|
id: 'toaster1',
|
||||||
title: 'Title',
|
title: 'Text',
|
||||||
message: 'Message',
|
message: 'Message',
|
||||||
status: StatusEnum.info,
|
status: StatusEnum.info,
|
||||||
})
|
})
|
||||||
|
@ -76,7 +76,7 @@ export const OutsideReact: Story = {
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
showToaster({
|
showToaster({
|
||||||
id: 'toaster2',
|
id: 'toaster2',
|
||||||
title: 'Title',
|
title: 'Text',
|
||||||
message: 'Message',
|
message: 'Message',
|
||||||
status: StatusEnum.success,
|
status: StatusEnum.success,
|
||||||
})
|
})
|
||||||
|
|
27
stories/typography/Introduction.mdx
Normal file
27
stories/typography/Introduction.mdx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Meta } from '@storybook/blocks';
|
||||||
|
|
||||||
|
<Meta title="Typography/Introduction" />
|
||||||
|
|
||||||
|
# Typography
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
Component for show text base on size is `Text` from `import { Text } from '@procyon/components/Text';`
|
||||||
|
|
||||||
|
### Small (sm)
|
||||||
|
|
||||||
|
- Standard font size for all page objects
|
||||||
|
|
||||||
|
### Medium (md)
|
||||||
|
|
||||||
|
- Section title
|
||||||
|
- Multiple times per page
|
||||||
|
|
||||||
|
### Large (lg)
|
||||||
|
|
||||||
|
- Page title
|
||||||
|
- Only once
|
||||||
|
|
||||||
|
### Extra Large (xl)
|
||||||
|
|
||||||
|
- For single page sections, like login or registration
|
Loading…
Add table
Reference in a new issue