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
4 changes: 2 additions & 2 deletions .github/workflows/azure-static-web-apps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ jobs:
- name: Build And Deploy
id: builddeploy
uses: Azure/static-web-apps-deploy@v1
env:
REACT_APP_BACKEND_URL: ${{ secrets.REACT_APP_BACKEND_URL }}
with:
azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }}
repo_token: ${{ secrets.GITHUB_TOKEN }}
Expand All @@ -32,8 +34,6 @@ jobs:
api_location: ""
output_location: "dist"
app_build_command: 'npm run build'
env:
REACT_APP_BACKEND_URL: ${{ secrets.REACT_APP_BACKEND_URL }}

close_pull_request_job:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
Expand Down
5 changes: 5 additions & 0 deletions config/webpack.plugins.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

module.exports = [
new HtmlWebpackPlugin({
Expand All @@ -7,4 +8,8 @@ module.exports = [
favicon: "./public/favicon.ico",
inject: true,
}),
new webpack.DefinePlugin({
'process.env.REACT_APP_BACKEND_URL': JSON.stringify(process.env.REACT_APP_BACKEND_URL),
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development'),
}),
].filter(Boolean);
55 changes: 55 additions & 0 deletions src/app/stores/favorites-store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { makeAutoObservable } from 'mobx';

export default class FavoritesStore {
private static storageKey = 'streetcode_favorites';
public favoriteIds: Set<number> = new Set();

public constructor() {
makeAutoObservable(this);
this.loadFromLocalStorage();
}

private loadFromLocalStorage = () => {
const stored = localStorage.getItem(FavoritesStore.storageKey);
if (stored) {
this.favoriteIds = new Set(JSON.parse(stored));
}
};

private saveToLocalStorage = () => {
localStorage.setItem(
FavoritesStore.storageKey,
JSON.stringify(Array.from(this.favoriteIds))
);
};

public addFavorite = (streetcodeId: number) => {
this.favoriteIds.add(streetcodeId);
this.saveToLocalStorage();
};

public removeFavorite = (streetcodeId: number) => {
this.favoriteIds.delete(streetcodeId);
this.saveToLocalStorage();
};

public toggleFavorite = (streetcodeId: number) => {
if (this.isFavorite(streetcodeId)) {
this.removeFavorite(streetcodeId);
} else {
this.addFavorite(streetcodeId);
}
};

public isFavorite = (streetcodeId: number): boolean => {
return this.favoriteIds.has(streetcodeId);
};

get favoritesCount() {
return this.favoriteIds.size;
}

get favoritesList() {
return Array.from(this.favoriteIds);
}
}
5 changes: 4 additions & 1 deletion src/app/stores/root-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import StreetcodesByTagStore from './streetcodes-bytag-store';
import TeamStore from './team-store';
import ToponymStore from './toponym-store';
import UserLoginStore from './user-login-store';
import FavoritesStore from './favorites-store';

interface Store {
factsStore: FactsStore,
Expand Down Expand Up @@ -63,6 +64,7 @@ interface Store {
relatedByTag: StreetcodesByTagStore,
createUpdateMediaStore: CreateUpdateMediaStore,
modalStore: ModalStore,
favoritesStore: FavoritesStore,
}

export interface StreetcodeDataStore {
Expand Down Expand Up @@ -101,7 +103,8 @@ export const store: Store = {
streetcodeMainPageStore: new StreetcodesMainPageStore(),
relatedByTag: new StreetcodesByTagStore(),
createUpdateMediaStore: new CreateUpdateMediaStore(),
modalStore: new ModalStore()
modalStore: new ModalStore(),
favoritesStore: new FavoritesStore()
};
export const streetcodeDataStore:StreetcodeDataStore = {
streetcodeStore: new StreetcodeStore(),
Expand Down
25 changes: 22 additions & 3 deletions src/features/StreetcodeCatalogPage/StreetcodeCatalog.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ import { useAsync } from '@/app/common/hooks/stateful/useAsync.hook';
import StreetcodeCatalogItem from './StreetcodeCatalogItem/StreetcodeCatalogItem.component';

const StreetcodeCatalog = () => {
const { streetcodeCatalogStore } = useMobx();
const { streetcodeCatalogStore, favoritesStore } = useMobx();
const { fetchCatalogStreetcodes, getCatalogStreetcodesArray } = streetcodeCatalogStore;
const [loading, setLoading] = useState(false);
const [screen, setScreen] = useState(1);
const [showOnlyFavorites, setShowOnlyFavorites] = useState(false);

const handleSetNextScreen = () => {
setScreen(screen + 1);
};

const filteredStreetcodes = showOnlyFavorites
? getCatalogStreetcodesArray.filter((streetcode) => favoritesStore.isFavorite(streetcode.id))
: getCatalogStreetcodesArray;

useAsync(async () => {
const count = await StreetcodesApi.getCount();
if (count === getCatalogStreetcodesArray.length) {
Expand All @@ -37,14 +42,28 @@ const StreetcodeCatalog = () => {
<div className="catalogPage">
<div className="streetcodeCatalogWrapper">
<h1 className="streetcodeCatalogHeading">Стріткоди</h1>
<div className="catalogFilterButtons">
<button
className={`filterButton ${!showOnlyFavorites ? 'active' : ''}`}
onClick={() => setShowOnlyFavorites(false)}
>
Усі стріткоди
</button>
<button
className={`filterButton ${showOnlyFavorites ? 'active' : ''}`}
onClick={() => setShowOnlyFavorites(true)}
>
Улюблені стріткоди
</button>
</div>
<div className="steetcodeCatalogContainer">
{
getCatalogStreetcodesArray.map(
filteredStreetcodes.map(
(streetcode, index) => (
<StreetcodeCatalogItem
key={streetcode.id}
streetcode={streetcode}
isLast={index === getCatalogStreetcodesArray.length - 1}
isLast={index === filteredStreetcodes.length - 1}
handleNextScreen={handleSetNextScreen}
/>
),
Expand Down
29 changes: 29 additions & 0 deletions src/features/StreetcodeCatalogPage/StreetcodeCatalog.styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,35 @@ $loadImg: '@assets/images/catalog/loading.gif';
align-items: center;
color: c.$milky-white-color;
}

.catalogFilterButtons {
@include mut.flexed($gap: pxToRem(12px));
@include mut.rem-margined($bottom: 24px);

.filterButton {
@include mut.rem-padded(10px, 20px, 10px, 20px);
@include mut.full-rounded(pxToRem(8px));
@include vnd.vendored(transition, 'all 0.2s ease');

cursor: pointer;
font-size: pxToRem(16px);
background: c.$pure-white-color;
color: c.$lighter-black-color;
border: 1px solid c.$darker-gray-color;
font-weight: normal;

&.active {
border: 2px solid c.$dark-red-color;
background: c.$dark-red-color;
color: c.$pure-white-color;
font-weight: bold;
}

&:hover {
opacity: 0.9;
}
}
}

.steetcodeCatalogContainer {
display: grid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import './StreetcodeCatalogItem.styles.scss';
import { observer } from 'mobx-react-lite';
import { useEffect, useRef } from 'react';
import { Link } from 'react-router-dom';
import { HeartFilled, HeartOutlined } from '@ant-design/icons';
import useMobx from '@stores/root-store';

import useOnScreen from '@/app/common/hooks/scrolling/useOnScreen.hook';
Expand All @@ -20,7 +21,7 @@ interface Props {
}

const StreetcodeCatalogItem = ({ streetcode, isLast, handleNextScreen }: Props) => {
const { imagesStore: { getImage, fetchImage } } = useMobx();
const { imagesStore: { getImage, fetchImage }, favoritesStore } = useMobx();
const elementRef = useRef<HTMLDivElement>(null);
const classSelector = 'catalogItem';
const isOnScreen = useOnScreen(elementRef, classSelector);
Expand All @@ -38,26 +39,43 @@ const StreetcodeCatalogItem = ({ streetcode, isLast, handleNextScreen }: Props)
}
const windowsize = useWindowSize();

const handleFavoriteClick = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
favoritesStore.toggleFavorite(streetcode.id);
};

const isFavorited = favoritesStore.isFavorite(streetcode.id);

return (
<>
{windowsize.width > 1024 && (
<Link {...LinkProps} onClick={() => toStreetcodeRedirectClickEvent(streetcode.url, 'catalog')}>
<div ref={elementRef} className="catalogItemText">
<div className="heading">
<p>{streetcode.title}</p>
{
streetcode.alias !== null ? (
<p className="aliasText">
({streetcode.alias})
</p>
) : undefined
}
<div style={{ position: 'relative' }}>
<Link {...LinkProps} onClick={() => toStreetcodeRedirectClickEvent(streetcode.url, 'catalog')}>
<div ref={elementRef} className="catalogItemText">
<div className="heading">
<p>{streetcode.title}</p>
{
streetcode.alias !== null ? (
<p className="aliasText">
({streetcode.alias})
</p>
) : undefined
}
</div>
</div>
</div>
</Link>
</Link>
<button
onClick={handleFavoriteClick}
aria-label={isFavorited ? 'Видалити з улюбленого' : 'Додати в улюблене'}
className="favoriteButton"
>
{isFavorited ? <HeartFilled style={{ color: '#C12828' }} /> : <HeartOutlined style={{ color: '#666' }} />}
</button>
</div>
)}
{windowsize.width <= 1024 && (
<div>
<div style={{ position: 'relative' }}>
<Link {...LinkProps} />
<div ref={elementRef} className="catalogItemText mobile">
<div className="heading">
Expand All @@ -71,6 +89,13 @@ const StreetcodeCatalogItem = ({ streetcode, isLast, handleNextScreen }: Props)
}
</div>
</div>
<button
onClick={handleFavoriteClick}
aria-label={isFavorited ? 'Видалити з улюбленого' : 'Додати в улюблене'}
className="favoriteButton mobile"
>
{isFavorited ? <HeartFilled style={{ color: '#C12828' }} /> : <HeartOutlined style={{ color: '#666' }} />}
</button>
</div>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,33 @@
}
}

.favoriteButton {
@include mut.positioned-as(absolute);
top: pxToRem(12px);
right: pxToRem(12px);
@include mut.sized(pxToRem(43px), pxToRem(43px));
@include mut.flex-centered();

background: rgba(255, 255, 255, 0.9);
border: none;
border-radius: 50%;
cursor: pointer;
font-size: pxToRem(22px);
@include vnd.vendored(transition, 'all 0.2s ease');
box-shadow: 0 pxToRem(2px) pxToRem(8px) rgba(0, 0, 0, 0.15);
z-index: 10;

&:hover {
@include vnd.vendored(transform, 'scale(1.1)');
box-shadow: 0 pxToRem(4px) pxToRem(12px) rgba(0, 0, 0, 0.25);
}

&.mobile {
@include mut.sized(pxToRem(40px), pxToRem(40px));
font-size: pxToRem(20px);
}
}

@media screen and (max-width: 1024px) {
.catalogItem {
@include mut.sized($width: 165px, $height: 165px);
Expand Down
Loading