Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ module.exports = {
'react/jsx-props-no-spreading': 'off',
'react/function-component-definition': 'off',
'react/require-default-props': 'off',
'no-plusplus': 'off',
'no-param-reassign': 'off,',
},
};
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
"dependencies": {
"@emotion/react": "^11.8.2",
"@emotion/styled": "^11.8.1",
"@reduxjs/toolkit": "^1.8.0",
"@types/react-redux": "^7.1.23",
"@types/yup": "^0.29.13",
"emotion-normalize": "^11.0.1",
"firebase": "^9.6.8",
"formik": "^2.2.9",
"next": "12.1.0",
"next-redux-wrapper": "^7.0.5",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-icons": "^4.3.1",
"react-loading-icons": "^1.0.8",
"react-redux": "^7.2.6",
"yup": "^0.32.11"
},
"devDependencies": {
Expand Down Expand Up @@ -51,6 +55,7 @@
"eslint-plugin-react-hooks": "^4.3.0",
"firebase-tools": "^10.3.0",
"prettier": "^2.5.1",
"redux-logger": "^3.0.6",
"typescript": "4.6.2"
}
}
Empty file removed src/api/.keep
Empty file.
9 changes: 9 additions & 0 deletions src/api/firebaseConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const firebaseConfig = {
apiKey: 'AIzaSyAqYYl92NFHBIwpT3l-e0Z3tXkXNt-WRAA',
authDomain: 'hanspoon-31cd9.firebaseapp.com',
projectId: 'hanspoon-31cd9',
storageBucket: 'hanspoon-31cd9.appspot.com',
messagingSenderId: '301891970054',
appId: '1:301891970054:web:4538a1e0b0a74fbff64f87',
measurementId: 'G-XB0QC0WFV5',
};
47 changes: 47 additions & 0 deletions src/components/SkeletonCard/SkeletonCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { SkeletonCard } from './SkeletonCard';

export default {
title: 'SkeletonCard',
component: SkeletonCard,
args: {
type: 'square',
background: 'white',
hasSummary: true,
headingPosition: 'topLeft',
},
} as ComponentMeta<typeof SkeletonCard>;

const Template: ComponentStory<typeof SkeletonCard> = (args) => <SkeletonCard {...args} />;

export const Default = Template.bind({});

export const WideType = Template.bind({});

WideType.args = {
type: 'wide',
};

export const NoBackground = Template.bind({});

NoBackground.args = {
background: 'none',
};

export const NoSummary = Template.bind({});

NoSummary.args = {
hasSummary: false,
};

export const BottomLeft = Template.bind({});

BottomLeft.args = {
headingPosition: 'bottomLeft',
};

export const BottomCenter = Template.bind({});

BottomCenter.args = {
headingPosition: 'bottomCenter',
};
126 changes: 126 additions & 0 deletions src/components/SkeletonCard/SkeletonCard.styled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import styled from '@emotion/styled';
import { css, keyframes } from '@emotion/react';
import {
SkeletonSummaryProps,
SkeletonTitleProps,
SkeletonTypeProps,
SkeletonWrapperProps,
} from './SkeletonCard.types';
import { media, pxToRem } from 'utils';

const skeleton = keyframes`
0% {
background-color: rgba(165, 165, 165, 0.1);
}

50% {
background-color: rgba(165, 165, 165, 0.3);
}

100% {
background-color: rgba(165, 165, 165, 0.1);
}
`;

const inlineBlock = css`
display: flex;
justify-content: center;
`;

const squareType = css`
width: ${pxToRem(200)};
height: ${pxToRem(200)};
background: gray;
`;

const wideType = css`
width: 100%;
height: 50vw;
background: gray;

${media.desktop} {
width: 100%;
height: 250px;
}
`;

const hasSummaryTrue = css`
display: block;
width: 100%;
height: ${pxToRem(20)};
background: gray;
position: relative;
overflow: hidden;

-webkit-animation: ${skeleton} 1.8s infinite ease-in-out;
animation: ${skeleton} 1.8s infinite ease-in-out;
`;

const hasSummaryFalse = css`
display: none;
`;

const bottomLeft = css`
background: gray;
left: 0;
`;
const bottomCenter = css`
background: gray;
align-self: center;
`;

const topLeft = css`
background: gray;
order: -1;
`;

export const SkeletonContainer = styled.div<SkeletonTypeProps>`
${(props) => props.$type === 'square' && inlineBlock}
`;

export const SkeletonCardWrapper = styled.div<SkeletonWrapperProps>`
${(props) => props.$type === 'square' && inlineBlock};

display: flex;
flex-direction: column;
padding: ${pxToRem(16)};
position: relative;

background: ${(props) => (props.$background === 'white' ? 'white' : 'none')};
box-shadow: ${(props) => props.$background === 'white' && '0px 4px 4px rgba(0, 0, 0, 0.25);'};
`;

export const SkeletonImage = styled.div<SkeletonTypeProps>`
${(props) => (props.$type === 'square' ? squareType : wideType)}

overflow: hidden;
position: relative;

-webkit-animation: ${skeleton} 1.8s infinite ease-in-out;
animation: ${skeleton} 1.8s infinite ease-in-out;
`;

export const SkeletonTitle = styled.div<SkeletonTitleProps>`
${(props) =>
props.$headingPosition === 'topLeft'
? topLeft
: props.$headingPosition === 'bottomCenter'
? bottomCenter
: bottomLeft}

margin: ${pxToRem(16)} 0 !important;
width: 70%;
max-width: 100%;
height: ${pxToRem(20)};
background: gray;
display: block;
overflow: hidden;
position: relative;

-webkit-animation: ${skeleton} 1.8s infinite ease-in-out;
animation: ${skeleton} 1.8s infinite ease-in-out;
`;

export const SkeletonSummary = styled.div<SkeletonSummaryProps>`
${(props) => (props.$hasSummary ? hasSummaryTrue : hasSummaryFalse)}
`;
20 changes: 20 additions & 0 deletions src/components/SkeletonCard/SkeletonCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
SkeletonCardWrapper,
SkeletonContainer,
SkeletonImage,
SkeletonSummary,
SkeletonTitle,
} from './SkeletonCard.styled';
import { SkeletonCardProps } from './SkeletonCard.types';

export const SkeletonCard = ({ type, background, hasSummary, headingPosition }: SkeletonCardProps): JSX.Element => {
return (
<SkeletonContainer $type={type}>
<SkeletonCardWrapper $type={type} $background={background}>
<SkeletonImage $type={type} />
<SkeletonTitle $headingPosition={headingPosition} />
<SkeletonSummary $hasSummary={hasSummary} />
</SkeletonCardWrapper>
</SkeletonContainer>
);
};
27 changes: 27 additions & 0 deletions src/components/SkeletonCard/SkeletonCard.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
type SkeletonType = 'wide' | 'square';
type SkeletonBackground = 'white' | 'none';
type SkeletonHeadingPosition = 'bottomLeft' | 'bottomCenter' | 'topLeft';

export interface SkeletonCardProps {
type: SkeletonType;
background: SkeletonBackground;
hasSummary: boolean;
headingPosition: SkeletonHeadingPosition;
}

export interface SkeletonTypeProps {
$type: SkeletonType;
}

export interface SkeletonWrapperProps {
$type: SkeletonType;
$background: SkeletonBackground;
}

export interface SkeletonTitleProps {
$headingPosition: SkeletonHeadingPosition;
}

export interface SkeletonSummaryProps {
$hasSummary: boolean;
}
3 changes: 2 additions & 1 deletion src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './Logo/Logo';
export * from './Loading/Loading';
export * from './Layout/Layout';
export * from './Header/Header';
export * from './EmptyPage/EmptyPage'
export * from './EmptyPage/EmptyPage';
export * from './SkeletonCard/SkeletonCard';
11 changes: 7 additions & 4 deletions src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ import { ThemeProvider } from '@emotion/react';
import { theme } from 'theme/theme';
import { GlobalStyle } from 'styles/GlobalStyle';
import { Layout } from '../components';
import { StoreProvider } from 'store';

function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Layout>
<Component {...pageProps} />
</Layout>
<StoreProvider>
<GlobalStyle />
<Layout>
<Component {...pageProps} />
</Layout>
</StoreProvider>
</ThemeProvider>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import type { NextPage } from 'next';
import { useGetRandomRecipeQuery } from 'store/services';
import Head from 'next/head';
import Image from 'next/image';

const Home: NextPage = () => {
const { data, error, isLoading } = useGetRandomRecipeQuery(1);
console.log(data);
console.log(error);
console.log(isLoading);

return <div>Home</div>;
};

Expand Down
15 changes: 15 additions & 0 deletions src/store/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { configureStore } from '@reduxjs/toolkit';
import { ReactNode } from 'react';
import { Provider } from 'react-redux';
import { twoSpoonApi } from 'store/services';

export const store = configureStore({
reducer: {
[twoSpoonApi.reducerPath]: twoSpoonApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(twoSpoonApi.middleware),
});

export const StoreProvider = (props: { children: ReactNode }) => {
return <Provider store={store} {...props} />;
};
24 changes: 24 additions & 0 deletions src/store/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { RandomRecipeQuery } from './types/queries';

export const twoSpoonApi = createApi({
reducerPath: 'twoSpoonApi',
baseQuery: fetchBaseQuery({
baseUrl: 'https://spoonacular-recipe-food-nutrition-v1.p.rapidapi.com/',
prepareHeaders: (headers) => {
headers.set('content-type', 'application/json');
headers.set('x-rapidapi-host', `${process.env.NEXT_PUBLIC_RAPID_API_HOST}`);
headers.set('x-rapidapi-key', `${process.env.NEXT_PUBLIC_RAPID_API_KEY}`);
return headers;
},
}),

endpoints: (builder) => ({
getRandomRecipe: builder.query<RandomRecipeQuery, number>({
query: (number = 1) => `recipes/random?number=${number}`,
// transformResponse: (response: { data: RandomRecipeQuery }) => response.data,
}),
}),
});

export const { useGetRandomRecipeQuery } = twoSpoonApi;
10 changes: 10 additions & 0 deletions src/store/services/types/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface RandomRecipe {
id: number;
title: string;
summary: string;
image: string;
}

export interface RandomRecipeQuery {
recipes: RandomRecipe[];
}
21 changes: 21 additions & 0 deletions src/store/slices/recipeReducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { createSlice } from '@reduxjs/toolkit';
import { RandomRecipe, RandomRecipeQuery } from 'store/services/types/queries';

interface RecipeState {
// savedRecipes: RandomRecipeQuery[];
recipes: RandomRecipe;
// curIndex: number;
}

// const initialState: RecipeState = { recipes: {} as RandomRecipe, curIndex: 0 };

// export const recipeSlice = createSlice({
// name: 'recipe',
// initialState,
// reducers: {
// nextRandomRecipe(state, payload) {
// state.curIndex++;
// state.recipes =
// },
// },
// });
1 change: 1 addition & 0 deletions src/utils/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const pxToRem = (px: number, base = 16): string => {

export const media = {
mobile: '@media(max-width:767px)',
desktop: '@media(min-width: 768px)',
};
Loading