diff --git a/public/img/fox.jpg b/public/img/fox.jpg new file mode 100644 index 000000000..add99e681 Binary files /dev/null and b/public/img/fox.jpg differ diff --git a/public/img/squirrel.jpg b/public/img/squirrel.jpg new file mode 100644 index 000000000..9c6c44add Binary files /dev/null and b/public/img/squirrel.jpg differ diff --git a/public/img/wolf.jpg b/public/img/wolf.jpg new file mode 100644 index 000000000..71d69a7f8 Binary files /dev/null and b/public/img/wolf.jpg differ diff --git a/public/locales/bg/beneficiary.json b/public/locales/bg/beneficiary.json index d7047fd9a..c145dc094 100644 --- a/public/locales/bg/beneficiary.json +++ b/public/locales/bg/beneficiary.json @@ -37,7 +37,7 @@ "company-select": "Изберете ЮЛНЦ или", "create-new": "създайте нов", "person-label": "Потребител", - "company-label": "ЮЛНЦ", + "company-label": "ЮЛНЦ" } }, "actions": "Действия", diff --git a/public/locales/bg/campaign-types.json b/public/locales/bg/campaign-types.json index fba885d0c..914c5fabc 100644 --- a/public/locales/bg/campaign-types.json +++ b/public/locales/bg/campaign-types.json @@ -3,7 +3,7 @@ "allCampaignTypes": "Всички типове кампании", "grid": { "name": "Име", - "category": "Категория", + "category": "Категория: ", "description": "Описание", "no-parent": "Няма категория", "no-description": "Няма описание" diff --git a/public/locales/bg/campaigns.json b/public/locales/bg/campaigns.json index eee6d3220..3aa97ff7e 100644 --- a/public/locales/bg/campaigns.json +++ b/public/locales/bg/campaigns.json @@ -39,6 +39,7 @@ "deleteTitle": "Сигурни ли сте?", "deleteContent": "Това действие ще изтрие елемента завинаги!", "actions": "Действия", + "toTheCampaignButton": "Към кампанията", "statistics": { "button": "Статистика за даренията", "backButton": "Обратно към кампанията", diff --git a/public/locales/bg/news.json b/public/locales/bg/news.json index 0d4aeb052..72ba6aec0 100644 --- a/public/locales/bg/news.json +++ b/public/locales/bg/news.json @@ -1,5 +1,6 @@ { "news": "Новини", + "all-news": "Всички новини", "form-heading": "Създайте нова статия", "edit-form-heading": "Редактирайте статия", "read-more": "Прочети повече", diff --git a/public/locales/en/bank-transactions.json b/public/locales/en/bank-transactions.json index 1e6146cfb..fdc23badd 100644 --- a/public/locales/en/bank-transactions.json +++ b/public/locales/en/bank-transactions.json @@ -1,27 +1,26 @@ { - "all": "All bank transactions", - "bank-transactions": "Bank transactions", - "transaction-date": "Transaction date", - "amount": "Amount", - "currency": "Currency", - "donation-status": "Donation status", - "id": "Trnsaction ID", - "bank-name": "Bank name", + "all": "All bank transactions", + "bank-transactions": "Bank transactions", + "transaction-date": "Transaction date", + "amount": "Amount", + "currency": "Currency", + "donation-status": "Donation status", + "id": "Trnsaction ID", + "bank-name": "Bank name", + "type": "Type", + "description": "Description", + "cta": { + "download": "Download", + "status": "Donation status", "type": "Type", - "description": "Description", - "cta": { - "download": "Download", - "status": "Donation status", - "type": "Type", - "from": "From", - "to": "To", - "apply-ref-heading": "Apply ref heading", - "apply-ref": "Apply ref", - "edit": "Edit", - "start-sync": "Start sync" - }, - "matched-ref": "Matched ref", - "payment-ref": "Payment ref", - "rerun-dates": "Repeat bank sync" - } - \ No newline at end of file + "from": "From", + "to": "To", + "apply-ref-heading": "Apply ref heading", + "apply-ref": "Apply ref", + "edit": "Edit", + "start-sync": "Start sync" + }, + "matched-ref": "Matched ref", + "payment-ref": "Payment ref", + "rerun-dates": "Repeat bank sync" +} diff --git a/public/locales/en/campaigns.json b/public/locales/en/campaigns.json index bbf5c8a92..6bcf973c0 100644 --- a/public/locales/en/campaigns.json +++ b/public/locales/en/campaigns.json @@ -39,6 +39,7 @@ "deleteTitle": "Are you sure you want to delete this campaign?", "deleteContent": "This action cannot be undone.", "actions": "Actions", + "toTheCampaignButton": "See the campaign", "statistics": { "button": "Donation statistics", "backButton": "Go back", @@ -89,6 +90,21 @@ "download": "Download", "allow-donation-on-complete": "Allow donations after the amount is reached" }, + "subscribe": { + "confirm-sent": "Please, activate your subscription from the email that we sent to {{email}}", + "confirm-subscribe": "You subscribed successfully", + "subscribe-title": "Subscribe for news from Podkrepi.bg", + "subscribe-campaign-title": "Subscribe for news about the campaign", + "subscribe-text-nonLoggedUser": "Please, proceed as a guest and write down your email, on which you want to receive notifications for this campaign or you can log in. If you log in with your and password you will be able to manage your subscription from your Personal profile", + "subscribe-text-nonLoggedUser-general": "Please, proceed as a guest and write down your email, on which you want to receive notifications from us or you can log in. If you log in with your and password you will be able to manage your subscription from your Personal profile", + "subscribe-text-loggedUser": "Please, choose if you want to receive the news about the campaign on your profile email or on another one:", + "subscribe-subtitle": "I want to receive news and notifications from Podkrepi.bg on this email:", + "subscribe-campaign-subtTitle": "I want to receive news about the campaign on this email:", + "subscribe-button": "Subscribe me", + "profile-button": "On the profile one", + "another-button": "On another one", + "monthly-newsletter": "Monthly newsletter" + }, "campaign": { "subheading": "Your support for the world matters. All supporters through Podkrepi.bg are our partners in supporting the community campaign. As a generous benefactor, you become an important partner in supporting a campaign regarding someone's health or the success of a cause that is close to your heart.", "title": "Campaign name", diff --git a/public/locales/en/news.json b/public/locales/en/news.json index 51b065432..79609db86 100644 --- a/public/locales/en/news.json +++ b/public/locales/en/news.json @@ -1,5 +1,6 @@ { "news": "News", + "all-news": "All news", "read-more": "Read more", "form-heading": "Create new article", "edit-form-heading": "Edit article", diff --git a/src/common/util/campaignImageUrls.ts b/src/common/util/campaignImageUrls.ts index 829bb1b77..ad1e8d9cd 100644 --- a/src/common/util/campaignImageUrls.ts +++ b/src/common/util/campaignImageUrls.ts @@ -1,5 +1,5 @@ import getConfig from 'next/config' -import { CampaignFile, CampaignResponse } from 'gql/campaigns' +import { CampaignFile } from 'gql/campaigns' import { CampaignFileRole, ImageSlider } from 'components/common/campaign-file/roles' const { publicRuntimeConfig } = getConfig() @@ -11,20 +11,20 @@ export function fileUrl(file: CampaignFile) { /** * Finds first file with given role */ -function findFileWithRole(campaign: CampaignResponse, role: CampaignFileRole) { - return campaign?.campaignFiles?.find((file) => file.role == role) +function findFileWithRole(campaignFile: CampaignFile[], role: CampaignFileRole) { + return campaignFile?.find((file) => file.role == role) } /** * Finds all files with given role */ -function filterFilesWithRole(campaign: CampaignResponse, role: CampaignFileRole[]) { - return campaign.campaignFiles.filter((file) => role.includes(file.role)) +function filterFilesWithRole(campaignFile: CampaignFile[], role: CampaignFileRole[]) { + return campaignFile.filter((file) => role.includes(file.role)) } -export function campaignSliderUrls(campaign: CampaignResponse): ImageSlider[] { +export function campaignSliderUrls(campaignFile: CampaignFile[]): ImageSlider[] { const sliderImageRoles = [CampaignFileRole.campaignPhoto, CampaignFileRole.gallery] - const files = filterFilesWithRole(campaign, sliderImageRoles) + const files = filterFilesWithRole(campaignFile, sliderImageRoles) const fileExtensionRemoverRegex = /.\w*$/ return files.map((file) => { return { @@ -35,27 +35,27 @@ export function campaignSliderUrls(campaign: CampaignResponse): ImageSlider[] { }) } -export function campaignListPictureUrl(campaign: CampaignResponse): string { - const file = findFileWithRole(campaign, CampaignFileRole.campaignListPhoto) +export function campaignListPictureUrl(campaignFile: CampaignFile[]): string { + const file = findFileWithRole(campaignFile, CampaignFileRole.campaignListPhoto) return file ? fileUrl(file) : '/podkrepi-icon.svg' } -export function backgroundCampaignPictureUrl(campaign: CampaignResponse): string { - const file = findFileWithRole(campaign, CampaignFileRole.background) +export function backgroundCampaignPictureUrl(campaignFile: CampaignFile[]): string { + const file = findFileWithRole(campaignFile, CampaignFileRole.background) return file ? fileUrl(file) : '/img/campaign-banner.png' } -export function coordinatorCampaignPictureUrl(campaign: CampaignResponse): string { - const file = findFileWithRole(campaign, CampaignFileRole.coordinator) +export function coordinatorCampaignPictureUrl(campaignFile: CampaignFile[]): string { + const file = findFileWithRole(campaignFile, CampaignFileRole.coordinator) return file ? fileUrl(file) : '/podkrepi-icon.png' } -export function organizerCampaignPictureUrl(campaign: CampaignResponse): string { - const file = findFileWithRole(campaign, CampaignFileRole.organizerPhoto) +export function organizerCampaignPictureUrl(campaignFile: CampaignFile[]): string { + const file = findFileWithRole(campaignFile, CampaignFileRole.organizerPhoto) return file ? fileUrl(file) : '/podkrepi-icon.png' } -export function beneficiaryCampaignPictureUrl(campaign: CampaignResponse): string { - const file = findFileWithRole(campaign, CampaignFileRole.beneficiaryPhoto) +export function beneficiaryCampaignPictureUrl(campaignFile: CampaignFile[]): string { + const file = findFileWithRole(campaignFile, CampaignFileRole.beneficiaryPhoto) return file ? fileUrl(file) : '/podkrepi-icon.png' } diff --git a/src/components/client/campaign-news/CampaignNewsList.tsx b/src/components/client/campaign-news/CampaignNewsList.tsx index 3d2a5277d..0307e391f 100644 --- a/src/components/client/campaign-news/CampaignNewsList.tsx +++ b/src/components/client/campaign-news/CampaignNewsList.tsx @@ -1,24 +1,50 @@ -import { Button, Grid, Link, Typography } from '@mui/material' - -import { CampaignNewsResponse } from 'gql/campaign-news' - +import { useTranslation } from 'react-i18next' +import { useRouter } from 'next/router' +import Image from 'next/image' +import { CampaignNewsListResponse } from 'gql/campaign-news' +import { Button, Grid, Typography, IconButton } from '@mui/material' import { styled } from '@mui/material/styles' -import { dateToTime, formatDateString } from 'common/util/date' import AvTimerIcon from '@mui/icons-material/AvTimer' import SupervisedUserCircleOutlinedIcon from '@mui/icons-material/SupervisedUserCircleOutlined' -import { useTranslation } from 'next-i18next' - -import Image from 'next/image' +import AnalyticsIcon from '@mui/icons-material/Analytics' +import { + Apartment, + Brush, + BusAlert, + Category, + Forest, + MedicalServices, + Pets, + School, + SportsTennis, + TheaterComedy, + VolunteerActivism, +} from '@mui/icons-material' +import theme from 'common/theme' +import { routes } from 'common/routes' +import { campaignListPictureUrl } from 'common/util/campaignImageUrls' +import { dateToTime, formatDateString } from 'common/util/date' import { GetArticleDocuments, GetArticleGalleryPhotos } from 'common/util/newsFilesUrls' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' import { useShowMoreContent } from './hooks/useShowMoreContent' -import { HTMLContentSeparator } from 'common/util/htmlUtils' -import theme from 'common/theme' -import { QuillStypeWrapper } from 'components/common/QuillStyleWrapper' import { scrollToTop } from './utils/scrollToTop' import { getArticleHeight } from './utils/getArticleHeight' -import Gallery from 'components/common/Gallery' - +const categories: { + [key in CampaignTypeCategory]: { icon?: React.ReactElement } +} = { + medical: { icon: }, + charity: { icon: }, + disasters: { icon: }, + education: { icon: }, + events: { icon: }, + environment: { icon: }, + sport: { icon: }, + art: { icon: }, + animals: { icon: }, + nature: { icon: }, + others: {}, +} const PREFIX = 'CampaignNewsSection' const classes = { defaultPadding: `${PREFIX}-defaultPadding`, @@ -28,11 +54,17 @@ const classes = { articleHeader: `${PREFIX}-articleHeader`, articleDescription: `${PREFIX}-articleDescription`, readMoreButton: `${PREFIX}-readMoreButton`, + campaignTitle: `${PREFIX}-campaignTitle`, } const ArticleSection = styled(Grid)(({ theme }) => ({ - paddingTop: theme.spacing(2), paddingBottom: theme.spacing(2), + display: 'grid', + [`& .${classes.articleDescription}`]: { + [theme.breakpoints.down('sm')]: { + display: 'none', + }, + }, [`& .${classes.articlepublishedDate}`]: { fontSize: theme.typography.pxToRem(14), @@ -54,10 +86,19 @@ const ArticleSection = styled(Grid)(({ theme }) => ({ paddingRight: theme.spacing(1), }, + [`& .${classes.campaignTitle}`]: { + maxWidth: theme.spacing(70), + fontWeight: 700, + }, + [`& .${classes.articleHeader}`]: { - fontSize: theme.typography.pxToRem(16), maxWidth: theme.spacing(70), fontWeight: 700, + + '&:hover': { + textDecoration: 'underline', + cursor: 'pointer', + }, }, [`& .${classes.dateAndAuthorContainer}`]: { @@ -75,120 +116,209 @@ const ArticleSection = styled(Grid)(({ theme }) => ({ }, })) +// These images are only for development stage +const images = ['/img/fox.jpg', '/img/squirrel.jpg', '/img/wolf.jpg'] + type Props = { - articles: CampaignNewsResponse[] | [] + articles: CampaignNewsListResponse[] | [] } +const StatusText = styled('span')(() => ({ + fontSize: theme.typography.pxToRem(14), +})) +const StatusLabel = styled(Typography)(() => ({ + fontSize: theme.typography.pxToRem(14), + fontWeight: 700, + marginRight: theme.spacing(1), +})) export default function CampaignNewsList({ articles }: Props) { - const { t, i18n } = useTranslation('news') + const { t, i18n } = useTranslation() const INITIAL_HEIGHT_LIMIT = 400 const [isExpanded, expandContent] = useShowMoreContent() + const router = useRouter() return ( <> {articles?.map((article, index: number) => { - const documents = GetArticleDocuments(article.newsFiles) - const images = GetArticleGalleryPhotos(article.newsFiles) - const [, sanitizedDescription] = HTMLContentSeparator(article.description) + // The next two lines should be uncommented finally + // const documents = GetArticleDocuments(article.newsFiles) + // const images = GetArticleGalleryPhotos(article.newsFiles) + const campaignCardImage = campaignListPictureUrl(article.campaign.campaignFiles) + const titleImage = images[0] ?? campaignCardImage + const campaignImagesUrl = campaignListPictureUrl(article.campaign.campaignFiles) return ( - - - + {/* The following should be uncommented finally + {article.newsFiles.length > 0 && ( + + router.push(routes.campaigns.news.viewSingleArticle(article.slug))} + src={article.newsFiles[0].src} + alt={article.newsFiles[0].fileName} + style={{ objectFit: 'contain', + cursor: 'pointer' }} + /> + + )} */} + + + {/* The next Grid item should be deleted finally and it will be replaced by the above*/} + + {/* The next condition should be in force finally and the following should be uncommented + {article.newsFiles.length === 0 ? {images[index]} router.push(routes.campaigns.news.viewSingleArticle(article.slug))} + fill + style={{ + objectFit: 'contain', + cursor: 'pointer' + }} /> : + */} + {images[index]} router.push(routes.campaigns.news.viewSingleArticle(article.slug))} + style={{ + position: 'relative', + objectFit: 'contain', + cursor: 'pointer', + }} + /> + {/* } */} + + + + + + + {article.campaign.title} + + + router.push(routes.campaigns.news.viewSingleArticle(article.slug)) + }> + {article.title} + + + + + + + + {t('campaign-types:grid.category')} + + {article.campaign.campaignType.category} + + + + {t('campaigns:campaign.status')} + + + {t(`campaigns:campaign-status.${article.campaign.state}`)} + + + + + + + + {formatDateString(article.publishedAt, i18n.language)}   + {dateToTime(article.publishedAt, i18n.language)} + + + + + {article.author} + + + + {getArticleHeight(article.id) > INITIAL_HEIGHT_LIMIT && ( + + )} + + + + <> + + - {formatDateString(article.publishedAt, i18n?.language)}   - {dateToTime(article.publishedAt, i18n?.language)} + {formatDateString(article.publishedAt, i18n.language)}   + {dateToTime(article.publishedAt, i18n.language)} - + {article.author} - - INITIAL_HEIGHT_LIMIT && !isExpanded[article.id] - ? INITIAL_HEIGHT_LIMIT - : 'auto', - overflow: 'hidden', - maxWidth: 1200, - }}> - - {article.title} - - - - - {documents.map((file) => ( - - - - {file.fileName} - - - - ))} + + + {Object.values(CampaignTypeCategory).map((category) => { + if (category === article.campaign.campaignType.category) + return ( + + {categories[article.campaign.campaignType.category].icon ?? ( + + )} + + ) + })} + {article.campaign.campaignType.category} + + + + + + + {t(`campaigns:campaign-status.${article.campaign.state}`)} + - {article.newsFiles.length > 0 && ( - <> - - - {images.map((file) => { - return ( - - {file.fileName} - - ) - })} - - - - )} - {getArticleHeight(article.id) > INITIAL_HEIGHT_LIMIT && ( - - )} - + ) })} diff --git a/src/components/client/campaign-news/CampaignNewsPage.tsx b/src/components/client/campaign-news/CampaignNewsPage.tsx index 4ced2d01d..4cca7892e 100644 --- a/src/components/client/campaign-news/CampaignNewsPage.tsx +++ b/src/components/client/campaign-news/CampaignNewsPage.tsx @@ -1,21 +1,30 @@ -import React from 'react' +import React, { useState } from 'react' import { useTranslation } from 'next-i18next' -import { Typography, Grid, PaginationItem, Divider } from '@mui/material' -import Pagination from '@mui/material/Pagination' +import Link from 'next/link' +import dynamic from 'next/dynamic' +import { + Typography, + Grid, + PaginationItem, + Divider, + Card, + CardContent, + Pagination, +} from '@mui/material' +import { styled } from '@mui/material/styles' +import EmailIcon from '@mui/icons-material/Email' import theme from 'common/theme' - import { baseUrl, routes } from 'common/routes' -import Layout from 'components/client/layout/Layout' -import { styled } from '@mui/material/styles' - import { useCampaignNewsList } from 'common/hooks/campaign-news' - -import Link from 'next/link' - -import dynamic from 'next/dynamic' - +import Layout from 'components/client/layout/Layout' import BreadcrumbWrapper from 'components/common/BreadcrumbWrapper' +import RenderSubscribeModal from '../notifications/GeneralSubscribeModal' +import { + Subtitle, + SubscribeButton, + SubscribeHeading, +} from '../index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled' const CampaignNewsList = dynamic(() => import('./CampaignNewsList'), { ssr: false }) @@ -50,9 +59,9 @@ type Props = { } export default function CampaignNewsPage({ page, slug = null }: Props) { + const [subscribeIsOpen, setSubscribeOpen] = useState(false) const { data } = useCampaignNewsList(page, slug) const { t } = useTranslation('news') - //TODO: Fill breadcumbs dynamically const breadcumbData = [ { label: 'campaigns', url: routes.campaigns.index }, @@ -60,15 +69,15 @@ export default function CampaignNewsPage({ page, slug = null }: Props) { ] if (slug && data) { breadcumbData.splice(1, 0, { - label: data.campaign.title, - url: routes.campaigns.viewCampaignBySlug(data.campaign.slug), + label: data.campaign.campaignNews[0].title, + url: routes.campaigns.viewCampaignBySlug(data.campaign.campaignNews[0].slug), }) } return ( + + + + + {subscribeIsOpen && } + + + + + {t('common:notifications.subscribe-monthly-newsletter')} + + + + {t('common:notifications.subscribeGeneralSubtext')} + + + + setSubscribeOpen(true)} variant="contained"> + {t('common:notifications.subscribe-general-newsletter-button')} + + + + + + ) } diff --git a/src/components/client/campaign-news/SingleArticlePage.tsx b/src/components/client/campaign-news/SingleArticlePage.tsx index 287d89e05..b9058ddf1 100644 --- a/src/components/client/campaign-news/SingleArticlePage.tsx +++ b/src/components/client/campaign-news/SingleArticlePage.tsx @@ -1,19 +1,63 @@ -import { Grid, Typography } from '@mui/material' - +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' import { styled } from '@mui/material/styles' -import { formatDateString } from 'common/util/date' +import Image from 'next/image' +import Link from 'next/link' +import { useRouter } from 'next/router' + +import { Chip, Grid, Typography, Divider, Card, CardContent, Box, IconButton } from '@mui/material' import AvTimerIcon from '@mui/icons-material/AvTimer' +import { + Apartment, + Brush, + BusAlert, + Category, + Forest, + MedicalServices, + Pets, + School, + SportsTennis, + TheaterComedy, + VolunteerActivism, +} from '@mui/icons-material' import SupervisedUserCircleOutlinedIcon from '@mui/icons-material/SupervisedUserCircleOutlined' -import { useTranslation } from 'next-i18next' - -import Image from 'next/image' +import EmailIcon from '@mui/icons-material/Email' +import theme from 'common/theme' +import { routes } from 'common/routes' +import { useCampaignList } from 'common/hooks/campaigns' +import { useFindArticleBySlug } from 'common/hooks/campaign-news' +import { formatDateString } from 'common/util/date' +import { moneyPublic } from 'common/util/money' import { GetArticleDocuments, GetArticleGalleryPhotos } from 'common/util/newsFilesUrls' import { HTMLContentSeparator } from 'common/util/htmlUtils' -import { useFindArticleBySlug } from 'common/hooks/campaign-news' -import Layout from '../layout/Layout' -import Link from 'next/link' +import BreadcrumbWrapper from 'components/common/BreadcrumbWrapper' import { QuillStypeWrapper } from 'components/common/QuillStyleWrapper' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' +import Layout from '../layout/Layout' +import InlineDonation from '../campaigns/InlineDonation' +import RenderSubscribeModal from '../notifications/GeneralSubscribeModal' +import { + Subtitle, + SubscribeButton, + SubscribeHeading, +} from '../index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled' +import CampaignProgress from '../campaigns/CampaignProgress' +const categories: { + [key in CampaignTypeCategory]: { icon?: React.ReactElement } +} = { + medical: { icon: }, + charity: { icon: }, + disasters: { icon: }, + education: { icon: }, + events: { icon: }, + environment: { icon: }, + sport: { icon: }, + art: { icon: }, + animals: { icon: }, + nature: { icon: }, + others: {}, +} const PREFIX = 'CampaignNewsSection' const classes = { defaultPadding: `${PREFIX}-defaultPadding`, @@ -21,14 +65,30 @@ const classes = { articleAuthor: `${PREFIX}-articleAuthorAndDate`, articlepublishedDate: `${PREFIX}-articlePublishedDate`, articleHeader: `${PREFIX}-articleHeader`, + campaignTitle: `${PREFIX}-campaignTitle`, articleDescription: `${PREFIX}-articleDescription`, readMoreButton: `${PREFIX}-readMoreButton`, + title: `${PREFIX}-title`, } +const Root = styled(Layout)(({ theme }) => ({ + [`& .${classes.title}`]: { + fontWeight: 500, + fontSize: theme.typography.pxToRem(45), + lineHeight: theme.typography.pxToRem(60), + letterSpacing: theme.typography.pxToRem(-1.5), + marginBottom: theme.spacing(1), + }, + + '.ql-video, img': { + margin: '0 auto', + display: 'block', + }, +})) + const ArticleSection = styled(Grid)(({ theme }) => ({ paddingLeft: theme.spacing(7), paddingRight: theme.spacing(7), - paddingTop: theme.spacing(2), paddingBottom: theme.spacing(2), [`& .${classes.articlepublishedDate}`]: { @@ -48,7 +108,12 @@ const ArticleSection = styled(Grid)(({ theme }) => ({ }, [`& .${classes.articleHeader}`]: { - fontSize: theme.typography.pxToRem(16), + fontSize: theme.typography.pxToRem(20), + maxWidth: theme.spacing(70), + fontWeight: 700, + }, + + [`& .${classes.campaignTitle}`]: { maxWidth: theme.spacing(70), fontWeight: 700, }, @@ -79,77 +144,278 @@ type Props = { slug: string } +// This image should be deleted finally +const image = '/img/fox.jpg' + +const StatusText = styled('span')(() => ({ + fontSize: theme.typography.pxToRem(14), +})) +const StatusLabel = styled(Typography)(() => ({ + fontSize: theme.typography.pxToRem(14), + fontWeight: 700, + marginRight: theme.spacing(1), +})) + export default function SingleArticlePage({ slug }: Props) { const { data: article, isLoading, isError } = useFindArticleBySlug(slug) - const { i18n } = useTranslation('news') + const [subscribeIsOpen, setSubscribeOpen] = useState(false) + const { i18n, t } = useTranslation() + const router = useRouter() if (isLoading || isError) return + const { + data: campaign, + isLoading: isLoadingCampaignData, + isError: isErrorCampaignData, + } = useCampaignList() + if (isLoadingCampaignData || isErrorCampaignData) return + + const reachedAmount = moneyPublic( + campaign[0].summary + ? campaign[0].summary.reachedAmount + (campaign[0].summary.guaranteedAmount ?? 0) + : 0, + ) + const targetAmount = moneyPublic(campaign[0].targetAmount) + + const breadcumbData = [ + { label: t('campaigns:campaigns'), url: routes.campaigns.index }, + { label: t('news:all-news'), url: routes.campaigns.news.index }, + { label: article.title, url: '' }, + ] + const documents = GetArticleDocuments(article.newsFiles) const images = GetArticleGalleryPhotos(article.newsFiles) const [, sanitizedDescription] = HTMLContentSeparator(article.description) return ( - - - - - - - - {formatDateString(article.publishedAt, i18n?.language)} + + + + + {t('news:news')} + + + + + + + + + {article.campaign.title} + + + {article.title} - - - {article.author} + + + {Object.values(CampaignTypeCategory).map((category) => { + if (category === article.campaign.campaignType.category) + return ( + + {categories[article.campaign.campaignType.category].icon ?? ( + + )} + + ) + })} + {article.campaign.campaignType.category} + + + + + {formatDateString(article.publishedAt, i18n?.language)} + + + + + {t('campaign-types:grid.category')} + + {article.campaign.campaignType.category} + + + + {article.author} + + + + {t('campaigns:campaign.status')} + + {t(`campaigns:campaign-status.${article.campaign.state}`)} + - - - {article.title} - - - + + router.push(routes.campaigns.viewCampaignBySlug(article.campaign.slug)) + } + clickable + size="small" + /> + {/* {article.newsFiles.length > 0 && ( */} + + {' '} + {image} + + + + {reachedAmount} + + + {targetAmount} + + + + + - + + + + + router.push(routes.campaigns.viewCampaignBySlug(article.campaign.slug)) + } + clickable + /> - - {documents.map((file) => ( + + + + + + + + {documents.map((file) => ( + + + + {file.fileName} + + + + ))} + + + + {images.map((file) => ( - - - {file.fileName} - - + {file.id} ))} - - {images.map((file) => ( - - {file.id} - - ))} + + + + {subscribeIsOpen && } + + + + {t('notifications.subscribe-monthly-newsletter')} + + + + {t('notifications.subscribeGeneralSubtext')} + + + + setSubscribeOpen(true)} variant="contained"> + {t('notifications.subscribe-general-newsletter-button')} + + + + + + + + - + - + ) } diff --git a/src/components/client/campaigns/CampaignCard/CampaignCard.tsx b/src/components/client/campaigns/CampaignCard/CampaignCard.tsx index cf1ab0d60..5b02c7dc7 100644 --- a/src/components/client/campaigns/CampaignCard/CampaignCard.tsx +++ b/src/components/client/campaigns/CampaignCard/CampaignCard.tsx @@ -38,7 +38,7 @@ export default function ActiveCampaignCard({ campaign, index }: Props) { allowDonationOnComplete, } = campaign - const campaignImagesUrl = campaignListPictureUrl(campaign) + const campaignImagesUrl = campaignListPictureUrl(campaign.campaignFiles) const reached = summary ? summary.reachedAmount + (summary.guaranteedAmount ?? 0) : 0 const reachedAmount = moneyPublic(reached) diff --git a/src/components/client/campaigns/CampaignDetails.tsx b/src/components/client/campaigns/CampaignDetails.tsx index c78e0a6db..1f0dff3ef 100644 --- a/src/components/client/campaigns/CampaignDetails.tsx +++ b/src/components/client/campaigns/CampaignDetails.tsx @@ -124,7 +124,7 @@ type Props = { export default function CampaignDetails({ campaign }: Props) { const { t } = useTranslation() const [subscribeIsOpen, setSubscribeOpen] = useState(false) - const sliderImages = campaignSliderUrls(campaign) + const sliderImages = campaignSliderUrls(campaign.campaignFiles) const canEditCampaign = useCanEditCampaign(campaign.slug) const { data: expensesList } = useCampaignApprovedExpensesList(campaign.slug) const totalExpenses = expensesList?.reduce((acc, expense) => acc + expense.amount, 0) diff --git a/src/components/client/campaigns/CampaignInfo/CampaignInfoBeneficiary.tsx b/src/components/client/campaigns/CampaignInfo/CampaignInfoBeneficiary.tsx index 9523c43f9..59fd8aa72 100644 --- a/src/components/client/campaigns/CampaignInfo/CampaignInfoBeneficiary.tsx +++ b/src/components/client/campaigns/CampaignInfo/CampaignInfoBeneficiary.tsx @@ -14,7 +14,7 @@ type Props = { } export default function CampaignInfoBeneficiary({ campaign }: Props) { - const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign) + const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign.campaignFiles) const { t } = useTranslation('campaigns') return ( diff --git a/src/components/client/campaigns/CampaignInfo/CampaignInfoOrganizer.tsx b/src/components/client/campaigns/CampaignInfo/CampaignInfoOrganizer.tsx index d440a6f4a..78c04aa22 100644 --- a/src/components/client/campaigns/CampaignInfo/CampaignInfoOrganizer.tsx +++ b/src/components/client/campaigns/CampaignInfo/CampaignInfoOrganizer.tsx @@ -15,7 +15,7 @@ type Props = { export default function CampaignInfoOrganizer({ campaign }: Props) { const { t } = useTranslation() - const organizerAvatarSource = organizerCampaignPictureUrl(campaign) + const organizerAvatarSource = organizerCampaignPictureUrl(campaign.campaignFiles) return ( diff --git a/src/components/client/campaigns/CampaignInfoOperator.tsx b/src/components/client/campaigns/CampaignInfoOperator.tsx index bbd8129fb..663778e37 100644 --- a/src/components/client/campaigns/CampaignInfoOperator.tsx +++ b/src/components/client/campaigns/CampaignInfoOperator.tsx @@ -64,7 +64,7 @@ type Props = { export default function CampaignInfoOperator({ campaign }: Props) { const { t } = useTranslation() - const coordinatorAvatarSource = coordinatorCampaignPictureUrl(campaign) + const coordinatorAvatarSource = coordinatorCampaignPictureUrl(campaign.campaignFiles) return ( diff --git a/src/components/client/campaigns/InlineDonation.tsx b/src/components/client/campaigns/InlineDonation.tsx index c41e32b6a..659d99420 100644 --- a/src/components/client/campaigns/InlineDonation.tsx +++ b/src/components/client/campaigns/InlineDonation.tsx @@ -42,6 +42,7 @@ import { socialMedia } from './helpers/socialMedia' import { CampaignState } from './helpers/campaign.enums' import { AlertStore } from 'stores/AlertStore' import { useDonationWishesList } from 'common/hooks/donationWish' +import { CampaignNewsResponse } from 'gql/campaign-news' const PREFIX = 'InlineDonation' diff --git a/src/components/client/campaigns/ViewCampaignPage.tsx b/src/components/client/campaigns/ViewCampaignPage.tsx index cde722832..407086224 100644 --- a/src/components/client/campaigns/ViewCampaignPage.tsx +++ b/src/components/client/campaigns/ViewCampaignPage.tsx @@ -26,7 +26,7 @@ export default function ViewCampaignPage({ slug }: Props) { ) const { campaign } = data - const ogImageUrl = campaignListPictureUrl(campaign) + const ogImageUrl = campaignListPictureUrl(campaign.campaignFiles) return ( const { campaign } = data - const bannerSource = backgroundCampaignPictureUrl(campaign) - const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign) + const bannerSource = backgroundCampaignPictureUrl(campaign.campaignFiles) + const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign.campaignFiles) return ( diff --git a/src/components/client/index/sections/ActiveCampaignsSection/ActiveCampaignCard/ActiveCampaignCard.tsx b/src/components/client/index/sections/ActiveCampaignsSection/ActiveCampaignCard/ActiveCampaignCard.tsx index 33d1d03d6..877a63702 100644 --- a/src/components/client/index/sections/ActiveCampaignsSection/ActiveCampaignCard/ActiveCampaignCard.tsx +++ b/src/components/client/index/sections/ActiveCampaignsSection/ActiveCampaignCard/ActiveCampaignCard.tsx @@ -27,8 +27,7 @@ type Props = { campaign: CampaignResponse; index: number } export default function ActiveCampaignCard({ campaign, index }: Props) { const { t } = useTranslation('campaigns') const { id, slug, title, summary, targetAmount: target } = campaign - const campaignImagesUrl = campaignListPictureUrl(campaign) - + const campaignImagesUrl = campaignListPictureUrl(campaign.campaignFiles) const reached = summary ? summary.reachedAmount + (summary.guaranteedAmount ?? 0) : 0 const reachedAmount = moneyPublic(reached) diff --git a/src/components/client/index/sections/CompletedCampaignsSection/CompletedCampaignsSection.tsx b/src/components/client/index/sections/CompletedCampaignsSection/CompletedCampaignsSection.tsx index 42e2ab02a..f6f19b8bc 100644 --- a/src/components/client/index/sections/CompletedCampaignsSection/CompletedCampaignsSection.tsx +++ b/src/components/client/index/sections/CompletedCampaignsSection/CompletedCampaignsSection.tsx @@ -57,7 +57,7 @@ export default function CompletedCampaignsSection() { {campaign.title} diff --git a/src/components/client/index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled.tsx b/src/components/client/index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled.tsx index 51c99674e..07f6dcb48 100644 --- a/src/components/client/index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled.tsx +++ b/src/components/client/index/sections/PlatformStatisticsSection/PlatformStatisticsSection.styled.tsx @@ -84,7 +84,6 @@ export const SubscribeButton = styled(Button)(() => ({ fontWeight: 600, borderRadius: theme.borders.round, backgroundColor: theme.palette.secondary.main, - minWidth: theme.spacing(3.75), fontSize: theme.typography.pxToRem(15), margin: theme.spacing(2, 0, 0, 'auto'), boxShadow: @@ -96,7 +95,7 @@ export const SubscribeButton = styled(Button)(() => ({ [theme.breakpoints.up('md')]: { fontSize: theme.typography.pxToRem(17), - minWidth: theme.spacing(50), + minWidth: theme.spacing(40), '& span': { display: 'inline-flex', diff --git a/src/components/client/one-time-donation/OneTimeDonationPage/OneTimeDonationPage.tsx b/src/components/client/one-time-donation/OneTimeDonationPage/OneTimeDonationPage.tsx index 253f45501..aba7b79af 100644 --- a/src/components/client/one-time-donation/OneTimeDonationPage/OneTimeDonationPage.tsx +++ b/src/components/client/one-time-donation/OneTimeDonationPage/OneTimeDonationPage.tsx @@ -39,7 +39,7 @@ export default function OneTimeDonation({ slug }: { slug: string }) { const { campaign } = data - const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign) + const beneficiaryAvatarSource = beneficiaryCampaignPictureUrl(campaign.campaignFiles) return ( diff --git a/src/gql/campaign-news.ts b/src/gql/campaign-news.ts index 15209a37d..b1c7164eb 100644 --- a/src/gql/campaign-news.ts +++ b/src/gql/campaign-news.ts @@ -1,6 +1,8 @@ import { ArticleStatus } from 'components/admin/campaign-news/helpers/article-status.enum' import { CampaignFileRole } from 'components/common/campaign-file/roles' import { UUID } from './types' +import { CampaignTypeCategory } from 'components/common/campaign-types/categories' +import { CampaignFile } from './campaigns' type BaseCampaignNewsResponse = { id: UUID @@ -16,6 +18,9 @@ type BaseCampaignNewsResponse = { editedAt: Date | undefined description: string newsFiles: CampaignNewsFile[] | [] + campaignType: { + name: string + } } export type AdminCampaignNewsResponse = BaseCampaignNewsResponse & { @@ -24,14 +29,21 @@ export type AdminCampaignNewsResponse = BaseCampaignNewsResponse & { } } -export type CampaignNewsResponse = Omit & { +export type CampaignNewsResponse = Omit & { campaign: { + id: string title: string state: string slug: string + campaignType: { + category: CampaignTypeCategory + } + campaignFiles: CampaignFile[] } } +export type CampaignNewsListResponse = Omit + export type CampaignNewsPagination = { currentPage: number nextPage: number @@ -41,9 +53,7 @@ export type CampaignNewsPagination = { export type CampaignNewsWithPaginationResponse = { campaign: { - title: string - slug: string - campaignNews: CampaignNewsResponse[] + campaignNews: CampaignNewsListResponse[] } pagination: CampaignNewsPagination } diff --git a/src/pages/campaigns/news/index.tsx b/src/pages/campaigns/news/index.tsx index 9da5b7346..a0af36973 100644 --- a/src/pages/campaigns/news/index.tsx +++ b/src/pages/campaigns/news/index.tsx @@ -17,7 +17,13 @@ export const getServerSideProps: GetServerSideProps = async ({ query, locale }) ) return { props: { - ...(await serverSideTranslations(locale ?? 'bg', ['common', 'breadcrumb', 'news'])), + ...(await serverSideTranslations(locale ?? 'bg', [ + 'common', + 'breadcrumb', + 'news', + 'campaigns', + 'campaign-types', + ])), page: page, dehydratedState: dehydrate(client), }, diff --git a/src/pages/news/[slug].tsx b/src/pages/news/[slug].tsx index 15d52253d..5b44b086a 100644 --- a/src/pages/news/[slug].tsx +++ b/src/pages/news/[slug].tsx @@ -30,6 +30,7 @@ export const getServerSideProps: GetServerSideProps = async ({ query, locale }) 'irregularity', 'expenses', 'news', + 'campaign-types', ])), dehydratedState: dehydrate(client), }, diff --git a/src/service/apiEndpoints.ts b/src/service/apiEndpoints.ts index 2b892a82a..a98442d19 100644 --- a/src/service/apiEndpoints.ts +++ b/src/service/apiEndpoints.ts @@ -78,7 +78,7 @@ export const endpoints = { listAdminNews: { url: '/campaign-news/list-all', method: 'GET' }, listAllNewsForCampaign: (slug: string) => { url: `/campaign-news/${slug}/list`, method: 'GET' }, - viewArticleBySlug: (slug: string) => { url: `/campaign-news/${slug}`, method: 'GET' }, + viewArticleBySlug: (slug: string) => { url: `/campaign/news/${slug}`, method: 'GET' }, viewNewsArticleById: (id: string) => { url: `/campaign-news/byId/${id}`, method: 'GET' }, editNewsArticle: (articleId: string) =>