Skip to content
Closed
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
4 changes: 3 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
REACT_APP_BACKEND_URL=https://localhost:5001/api
REACT_APP_TEMPVAL=tempval
REACT_APP_API_URL=https://localhost:5001/api
REACT_APP_API_URL=https://localhost:5001/api

REACT_APP_GOOGLE_CLIENT_ID=
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,11 @@ Visit our <a href="https://streetcode.com.ua" target="_blank">*site*</a>, we wil

- **[MIT license](http://opensource.org/licenses/mit-license.php)**
- Copyright 2022 © <a href="https://softserve.academy/" target="_blank"> SoftServe IT Academy</a>.



## . Create .env file in repository root example

```env
GoogleAuth__ClientId=123721973387-7i3rs06c8iui5lrb805f22o0k2s6gk1o.apps.googleusercontent.com
```
7 changes: 7 additions & 0 deletions config/webpack.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ module.exports = {
open: true,
port: "3000",
historyApiFallback: true,
client: {
// Показывать оверлеем только ошибки, без потока предупреждений.
overlay: {
errors: true,
warnings: false,
},
},
},
module: {
rules: require('./webpack.rules'),
Expand Down
17 changes: 16 additions & 1 deletion config/webpack.rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,22 @@ module.exports = [
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{ loader: 'sass-loader' },
{
loader: 'sass-loader',
options: {
// Заглушаем deprecation-предупреждения Dart Sass, которые
// флудят dev-сервер (по одному на каждый .scss-файл).
sassOptions: {
quietDeps: true, // тихо для зависимостей (node_modules, swiper)
silenceDeprecations: [
'legacy-js-api',
'import',
'global-builtin',
'color-functions',
],
},
},
},
],
}
].filter(Boolean);
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^4.8.0",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@dnd-kit/utilities": "^3.2.2",
"@fortawesome/fontawesome-free": "^6.4.0",
"@react-google-maps/api": "^2.18.1",
"@react-hook/resize-observer": "^1.2.6",
"@react-oauth/google": "^0.13.5",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
Expand Down
46 changes: 23 additions & 23 deletions src/app/api/agent.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,25 +37,25 @@ axios.interceptors.response.use(
errorMessage = message;
}
switch (response?.status) {
case StatusCodes.INTERNAL_SERVER_ERROR:
errorMessage = ReasonPhrases.INTERNAL_SERVER_ERROR;
break;
case StatusCodes.UNAUTHORIZED:
errorMessage = ReasonPhrases.UNAUTHORIZED;
UserLoginStore.clearUserData();
globalThis.location.href = FRONTEND_ROUTES.ADMIN.LOGIN;
break;
case StatusCodes.NOT_FOUND:
errorMessage = ReasonPhrases.NOT_FOUND;
break;
case StatusCodes.BAD_REQUEST:
errorMessage = getErrorMessage(response?.data) || ReasonPhrases.BAD_REQUEST;
break;
case StatusCodes.FORBIDDEN:
errorMessage = ReasonPhrases.FORBIDDEN;
break;
default:
break;
case StatusCodes.INTERNAL_SERVER_ERROR:
errorMessage = ReasonPhrases.INTERNAL_SERVER_ERROR;
break;
case StatusCodes.UNAUTHORIZED:
errorMessage = ReasonPhrases.UNAUTHORIZED;
UserLoginStore.clearUserData();
globalThis.location.href = FRONTEND_ROUTES.ADMIN.LOGIN;
break;
case StatusCodes.NOT_FOUND:
errorMessage = ReasonPhrases.NOT_FOUND;
break;
case StatusCodes.BAD_REQUEST:
errorMessage = getErrorMessage(response?.data) || ReasonPhrases.BAD_REQUEST;
break;
case StatusCodes.FORBIDDEN:
errorMessage = ReasonPhrases.FORBIDDEN;
break;
default:
break;
}
if (errorMessage !== '' && process.env.NODE_ENV === 'development') {
toast.error(errorMessage);
Expand All @@ -65,22 +65,22 @@ axios.interceptors.response.use(
},
);

const responseBody = <T> (response: AxiosResponse<T>) => response.data;
const responseBody = <T>(response: AxiosResponse<T>) => response.data;

const Agent = {
get: async <T> (url: string, params?: URLSearchParams) => {
get: async <T>(url: string, params?: URLSearchParams) => {
axios.defaults.headers.common.Authorization = `Bearer ${UserLoginStore.getToken()}`;
return axios.get<T>(url, { params })
.then(responseBody);
},

post: async <T> (url: string, body: object, headers?: object) => {
post: async <T>(url: string, body: object, headers?: object) => {
axios.defaults.headers.common.Authorization = `Bearer ${UserLoginStore.getToken()}`;
return axios.post<T>(url, body, headers)
.then(responseBody);
},

put: async <T> (url: string, body: object) => {
put: async <T>(url: string, body: object) => {
axios.defaults.headers.common.Authorization = `Bearer ${UserLoginStore.getToken()}`;
return axios.put<T>(url, body)
.then(responseBody);
Expand Down
9 changes: 9 additions & 0 deletions src/app/api/media/art-slide-templates.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Agent from '@api/agent.api';
import { API_ROUTES } from '@constants/api-routes.constants';
import { ArtSlideTemplate } from '@models/media/art-slide-template.model';

const ArtSlideTemplatesApi = {
getAll: () => Agent.get<ArtSlideTemplate[]>(API_ROUTES.ART_SLIDE_TEMPLATES.GET_ALL),
};

export default ArtSlideTemplatesApi;
22 changes: 22 additions & 0 deletions src/app/api/media/art-slide.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Agent from '@api/agent.api';
import { API_ROUTES } from '@constants/api-routes.constants';
import { ArtSlide, CreateArtSlide, UpdateArtSlide } from '@models/media/art-slide.model';

const ArtSlidesApi = {
getAllByStreetcodeId: (streetcodeId: number) =>
Agent.get<ArtSlide[]>(`${API_ROUTES.ART_SLIDES.GET_BY_STREETCODE_ID}/${streetcodeId}`),

create: (artSlide: CreateArtSlide) =>
Agent.post<ArtSlide>(`${API_ROUTES.ART_SLIDES.CREATE}`, artSlide),

createAll: (artSlides: CreateArtSlide[]) =>
Agent.post<ArtSlide[]>(`${API_ROUTES.ART_SLIDES.CREATE_ALL}`, artSlides),

update: (artSlide: UpdateArtSlide) =>
Agent.put<ArtSlide>(`${API_ROUTES.ART_SLIDES.UPDATE}`, artSlide),

delete: (id: number) =>
Agent.delete(`${API_ROUTES.ART_SLIDES.DELETE}/${id}`),
};

export default ArtSlidesApi;
5 changes: 3 additions & 2 deletions src/app/api/media/arts.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ const ArtsApi = {

getById: (id: number) => Agent.get<Art>(`${API_ROUTES.ARTS.GET}/${id}`),

create: (art: Art) => Agent.post<Art>(`${API_ROUTES.ARTS.CREATE}`, art),
create: (data: FormData) => Agent.post<Art>(`${API_ROUTES.ARTS.CREATE}`, data),

update: (art: Art) => Agent.post<Art>(`${API_ROUTES.ARTS.UPDATE}`, art),
update: (id: number, data: { title: string; description: string }) =>
Agent.put<Art>(`${API_ROUTES.ARTS.UPDATE}/${id}`, data),

delete: (id: number) => Agent.delete(`${API_ROUTES.ARTS.DELETE}/${id}`),
};
Expand Down
6 changes: 6 additions & 0 deletions src/app/api/user/user.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ import {
loginParams,
),

googleLogin: (body: { idToken: string }) =>
Agent.post<UserLoginResponce>(
API_ROUTES.ADMIN_AUTHORIZATION.GOOGLE_LOGIN,
body,
),

adminRefreshToken: (token: RefreshTokenRequest) =>
Agent.post<RefreshTokenResponce>(
API_ROUTES.ADMIN_AUTHORIZATION.REFRESH_TOKEN,
Expand Down
25 changes: 25 additions & 0 deletions src/app/common/components/ImageTemplates-grid/TemplateRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { observer } from 'mobx-react-lite';
import { TEMPLATE_CLASS_MAP } from '@constants/template.map';
import './shared-grid.styles.scss';

export const TemplateRenderer = observer(({ template, renderSlot, className }: any) => {

const config = TEMPLATE_CLASS_MAP[template?.name];

const slots = config?.slots ?? [];
const gap = config?.gap ?? 5;
const templateClass = config?.className || 'template-default';

return (
<div
className={`preview-grid ${templateClass} ${className || ''}`}
style={{ gap: `${gap}px` }}
>
{slots.map((slot: any) => (
<div key={slot.id} className="preview-slot">
{renderSlot(slot)}
</div>
))}
</div>
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
.preview-slot {
background: #000;
border-radius: 4px;
overflow: hidden;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;

img {
width: 100%;
height: 100%;
min-width: 100%;
min-height: 100%;
object-fit: cover;
object-position: center;
display: block;
}
}


.preview-grid {
position: relative;
display: grid;
gap: 5px;

// 1-2 слотф
&.template-single-large { grid-template-columns: 255px; grid-template-rows: 205px; grid-template-areas: "slot-0"; }
&.template-single-medium { grid-template-columns: 137px; grid-template-rows: 203px; grid-template-areas: "slot-0"; }
&.template-two-horizontal { grid-template-columns: 137px 137px; grid-template-rows: 203px; grid-template-areas: "slot-0 slot-1"; }

// 3 слота
&.template-three-mixed {
grid-template-columns: 137px 137px;
grid-template-rows: 203px 100px;
grid-template-areas: "slot-0 slot-1" "slot-2 slot-2";
}

// 4 слота
&.template-four-grid { grid-template-columns: repeat(2, 135px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-1" "slot-2 slot-3"; }
&.template-four-sidebar-left {
grid-template-columns: 135px 137px; grid-template-rows: 101px 101px;
grid-template-areas: "slot-0 slot-1" "slot-0 slot-2";
}
&.template-four-sidebar-right {
grid-template-columns: 137px 135px; grid-template-rows: 101px 101px;
grid-template-areas: "slot-1 slot-0" "slot-2 slot-0";
}

// 5 слотів
&.template-five-mixed { grid-template-columns: 137px 65px 65px; grid-template-rows: 101px 101px; grid-template-areas: "slot-0 slot-1 slot-2" "slot-0 slot-3 slot-4"; }
&.template-five-grid { grid-template-columns: repeat(3, 85px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-1 slot-2" "slot-3 slot-4 slot-4"; }
&.template-five-complex { grid-template-columns: repeat(2, 130px); grid-template-rows: repeat(3, 65px); grid-template-areas: "slot-0 slot-1" "slot-2 slot-3" "slot-4 slot-4"; }

// 6 слотів
&.template-six-grid { grid-template-columns: repeat(3, 85px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-1 slot-2" "slot-3 slot-4 slot-5"; }
&.template-six-compact { grid-template-columns: repeat(3, 85px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-1 slot-2" "slot-3 slot-4 slot-5"; }
&.template-six-mixed { grid-template-columns: repeat(3, 85px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-0 slot-1" "slot-2 slot-3 slot-4"; }
&.template-six-large { grid-template-columns: repeat(3, 85px); grid-template-rows: repeat(2, 101px); grid-template-areas: "slot-0 slot-1 slot-2" "slot-3 slot-4 slot-5"; }
}

.preview-grid.carousel-preview {
.preview-slot { border-radius: 30px; }

/* 1 слот */
&.template-single-large { grid-template-columns: 380px; grid-template-rows: 305px; }
&.template-single-medium { grid-template-columns: 205px; grid-template-rows: 305px; }

/* 2 слота */
&.template-two-horizontal { grid-template-columns: repeat(2, 205px); grid-template-rows: 305px; }

/* 3 слота */
&.template-three-mixed { grid-template-columns: 205px 205px; grid-template-rows: 305px 150px; gap: 6px; }

/* 4 слота */
&.template-four-grid { grid-template-columns: repeat(2, 200px); grid-template-rows: repeat(2, 150px); gap: 6px; }
&.template-four-sidebar-left {
grid-template-columns: 200px 205px; grid-template-rows: 150px 150px; gap: 6px;
.preview-slot:first-child { grid-row: span 2; }
}
&.template-four-sidebar-right {
grid-template-columns: 205px 200px; grid-template-rows: 150px 150px; gap: 6px;
.preview-slot:first-child { grid-row: span 2; }
}

/* 5 слотів */
&.template-five-mixed { grid-template-columns: 205px 100px 100px; grid-template-rows: 150px 150px; gap: 6px; }
&.template-five-grid { grid-template-columns: repeat(3, 130px); grid-template-rows: repeat(2, 150px); gap: 6px; }
&.template-five-complex { grid-template-columns: repeat(2, 200px); grid-template-rows: repeat(3, 100px); gap: 6px; }

/* 6 слотів */
&.template-six-grid { grid-template-columns: repeat(3, 130px); grid-template-rows: repeat(2, 150px); gap: 6px; }
&.template-six-compact { grid-template-columns: repeat(3, 130px); grid-template-rows: repeat(2, 150px); gap: 6px; }
&.template-six-mixed { grid-template-columns: repeat(3, 130px); grid-template-rows: repeat(2, 150px); gap: 6px; }
&.template-six-large { grid-template-columns: repeat(3, 130px); grid-template-rows: repeat(2, 150px); gap: 6px; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { observer } from 'mobx-react-lite';
import { useModalContext } from '@stores/root-store';
import { Modal, Button } from 'antd';

const DeleteImageModal = observer(() => {
const { modalStore: { setModal, modalsState: { deleteImage } } } = useModalContext();

const onConfirm = () => {
deleteImage.image?.onConfirm?.();
setModal('deleteImage', undefined, false);
};
return (
<Modal
title="Видалити"
open={deleteImage.isOpen}
onOk={onConfirm}
onCancel={() => setModal('deleteImage')}
footer={[
<Button
key="back"
onClick={() => setModal('deleteImage', undefined, false)}
className="ant-btn-default"
>
Скасувати
</Button>,
<Button
key="submit"
type="primary"
danger
onClick={onConfirm}
className="ant-btn-primary"
>
Видалити
</Button>,
]}
>
<p>Ви впевнені?</p>
</Modal>
);
});

export default DeleteImageModal;
Loading
Loading