diff --git a/frontend-next-migration/src/app/[lng]/(helper)/join-us/_getPage.ts b/frontend-next-migration/src/app/[lng]/(helper)/join-us/_getPage.ts index 997603549..381f5c685 100644 --- a/frontend-next-migration/src/app/[lng]/(helper)/join-us/_getPage.ts +++ b/frontend-next-migration/src/app/[lng]/(helper)/join-us/_getPage.ts @@ -2,38 +2,38 @@ import { createPage } from '@/app/_helpers'; import { JoinUsProps } from '@/preparedPages/JoinUsPage'; import { getServerTranslation } from '@/shared/i18n'; import { - makeDiscordBlock, - makeRedditBlock, - makeDuunitoriBlock, - makeFeedbackBlock, - makeTeachersBlock, - makeInstagramBlock, + makeGetInTouchAndFollow, + makeCommunityAndOpportunities, + makeEducationProfessionals, + makeFeedback, } from '@/entities/JoinUs'; import { getRouteJoinUsPage } from '@/shared/appLinks/RoutePaths'; import { defaultOpenGraph } from '@/shared/seoConstants'; export async function _getPage(lng: string) { const { t } = await getServerTranslation(lng, 'join-us'); + return createPage({ buildPage: () => ({ title: t('join-us'), - discordBlock: makeDiscordBlock(t), - connectionBlock: makeRedditBlock(t), - teachersBlock: makeTeachersBlock(t), - feedbackBlock: makeFeedbackBlock(t), - duunitoriBlock: makeDuunitoriBlock(t), - instagramBlock: makeInstagramBlock(t), + getInTouchAndFollowBlock: makeGetInTouchAndFollow(t), + communityAndOpportunitiesBlock: makeCommunityAndOpportunities(t), + educationProfessionalsBlock: makeEducationProfessionals(t), + feedbackBlock: makeFeedback(t), }), + buildSeo: () => ({ title: t('head-title'), description: t('head-description'), keywords: t('head-keywords'), + openGraph: { ...defaultOpenGraph, title: t('og-title'), description: t('og-description'), url: `/${lng}${getRouteJoinUsPage()}`, }, + alternates: { canonical: `/${lng}${getRouteJoinUsPage()}`, }, diff --git a/frontend-next-migration/src/entities/JoinUs/index.ts b/frontend-next-migration/src/entities/JoinUs/index.ts index e324a88a6..3fbceff3b 100644 --- a/frontend-next-migration/src/entities/JoinUs/index.ts +++ b/frontend-next-migration/src/entities/JoinUs/index.ts @@ -1,10 +1,8 @@ export { Block } from './ui/Block'; export { - makeDiscordBlock, - makeRedditBlock, - makeTeachersBlock, - makeDuunitoriBlock, - makeFeedbackBlock, - makeInstagramBlock, + makeGetInTouchAndFollow, + makeCommunityAndOpportunities, + makeEducationProfessionals, + makeFeedback, } from './model/makeJoinUsBlocks'; diff --git a/frontend-next-migration/src/entities/JoinUs/model/makeJoinUsBlocks.ts b/frontend-next-migration/src/entities/JoinUs/model/makeJoinUsBlocks.ts index ff7672542..70a2fdb38 100644 --- a/frontend-next-migration/src/entities/JoinUs/model/makeJoinUsBlocks.ts +++ b/frontend-next-migration/src/entities/JoinUs/model/makeJoinUsBlocks.ts @@ -1,11 +1,9 @@ import { BlockSection } from '../types'; import { AppExternalLinks } from '@/shared/appLinks/appExternalLinks'; -import ConnectionImage from '@/shared/assets/images/heros/mirror/Mirror.png'; -import teachersImage from '@/shared/assets/images/heros/sleeper/Sleeper_new.png'; -import feedbackImage from '@/shared/assets/images/heros/einstein/einstein.png'; -import duunitoriImage from '@/shared/assets/images/heros/purple-girls/purpel-girls-main.png'; -import discordImage from '@/shared/assets/images/heros/conman/conman.png'; -import instagramImage from '@/shared/assets/images/heros/fate-priest/Believer.png'; +import ArvotImage from '@/shared/assets/images/joinusPage/Arvot.png'; +import KaikkienKaveritImage from '@/shared/assets/images/joinusPage/KaikkienKaverit.png'; +import OpetusmaailmaImage from '@/shared/assets/images/joinusPage/Opetusmaailma.png'; +import ClanLabelImage from '@/shared/assets/images/joinusPage/ClanLabelSomePersons.png'; import igIcon from '@/shared/assets/images/Insta2.svg'; import fbdIcon from '@/shared/assets/images/Facebook2.svg'; import discordIcon from '@/shared/assets/images/Discord2.svg'; @@ -16,8 +14,8 @@ import ytIcon from '@/shared/assets/images/Youtube2.svg'; * Supports multiple links per block, internationalized text labels, descriptions, and an optional image. * * @param {string} section - - * A section identifier (e.g., "discord", "teachers", "feedback") that is used to construct the correct - * translation keys dynamically. These keys are combined to fetch the localized label, description, link texts, and alt texts. + * A section identifier used to construct translation keys dynamically. + * These keys are combined to fetch the localized label, description, link texts, and alt texts. * * @param {BlockSection['links']} link - * An array of link objects. Each object represents a link associated with the block and should include: @@ -37,7 +35,7 @@ import ytIcon from '@/shared/assets/images/Youtube2.svg'; * * @callback t * @param {string} key - - * The translation key used to fetch the localized string (e.g., "block-label-discord", "block-description-feedback"). + * The translation key used to fetch the localized string. * * @returns {string} - * The translated string value associated with the provided key. @@ -57,6 +55,7 @@ export const makeBlocksWithI18n = ( text: t(`block-link-text-${linkItem.text}`), isExternal: linkItem.isExternal ?? false, iconSrc: linkItem.iconSrc, + showExternalIcon: linkItem.showExternalIcon, })), img: img || '', imgAlt: t(`block-image-alt-${section}`), @@ -64,44 +63,10 @@ export const makeBlocksWithI18n = ( }; }; -export const makeDiscordBlock = makeBlocksWithI18n( - 'discord', - [{ text: 'discord', url: AppExternalLinks.discord, isExternal: true }], - discordImage.src.toString(), -); -export const makeRedditBlock = makeBlocksWithI18n( - 'connection', +export const makeGetInTouchAndFollow = makeBlocksWithI18n( + 'getInTouchAndFollow', [ - { text: 'email', url: 'mailto:proyaleg@gmail.com', isExternal: true }, - { text: 'phone', url: 'tel:+358442407396', isExternal: true }, - ], - ConnectionImage.src.toString(), -); -export const makeTeachersBlock = makeBlocksWithI18n( - 'teachers', - [ - { text: 'email', url: 'mailto:proyaleg@gmail.com', isExternal: true }, { text: 'phone', url: 'tel:+358442407396', isExternal: true }, - { text: 'teacherPg', url: AppExternalLinks.dlpackage, isExternal: true }, - ], - teachersImage.src.toString(), -); -export const makeFeedbackBlock = makeBlocksWithI18n( - 'feedback', - [ - { text: 'feedbackWep', url: AppExternalLinks.googleWebFeedback, isExternal: true }, - { text: 'feedbackGame', url: AppExternalLinks.googleFeedback, isExternal: true }, - ], - feedbackImage.src.toString(), -); -export const makeDuunitoriBlock = makeBlocksWithI18n( - 'duunitori', - [{ text: 'duunitori', url: AppExternalLinks.duunitori, isExternal: true }], - duunitoriImage.src.toString(), -); -export const makeInstagramBlock = makeBlocksWithI18n( - 'instagram', - [ { text: 'icone', url: AppExternalLinks.discord, @@ -126,6 +91,66 @@ export const makeInstagramBlock = makeBlocksWithI18n( isExternal: true, iconSrc: igIcon.src, }, + { text: 'email', url: 'mailto:proyaleg@gmail.com', isExternal: true }, + ], + ClanLabelImage.src.toString(), +); + +export const makeCommunityAndOpportunities = makeBlocksWithI18n( + 'communityAndOpportunities', + [ + { + text: 'discord', + url: AppExternalLinks.discord, + isExternal: true, + showExternalIcon: true, + }, + { + text: 'news', + url: '/fi/news', + isExternal: false, + showExternalIcon: false, + }, + { + text: 'duunitori', + url: AppExternalLinks.duunitori, + isExternal: true, + showExternalIcon: true, + }, + ], + KaikkienKaveritImage.src.toString(), +); + +export const makeEducationProfessionals = makeBlocksWithI18n( + 'educationProfessionals', + [ + { text: 'phone', url: 'tel:+358442407396', isExternal: true }, + { + text: 'teacherPg', + url: AppExternalLinks.dlpackage, + isExternal: true, + showExternalIcon: true, + }, + { text: 'email', url: 'mailto:proyaleg@gmail.com', isExternal: true }, + ], + OpetusmaailmaImage.src.toString(), +); + +export const makeFeedback = makeBlocksWithI18n( + 'feedback', + [ + { + text: 'feedbackWep', + url: AppExternalLinks.googleWebFeedback, + isExternal: true, + showExternalIcon: true, + }, + { + text: 'feedbackGame', + url: AppExternalLinks.googleFeedback, + isExternal: true, + showExternalIcon: true, + }, ], - instagramImage.src.toString(), + ArvotImage.src.toString(), ); diff --git a/frontend-next-migration/src/entities/JoinUs/types/index.ts b/frontend-next-migration/src/entities/JoinUs/types/index.ts index 01123d85a..5323432d1 100644 --- a/frontend-next-migration/src/entities/JoinUs/types/index.ts +++ b/frontend-next-migration/src/entities/JoinUs/types/index.ts @@ -16,6 +16,7 @@ export interface BlockSection { text: string; url: string; isExternal?: boolean; + showExternalIcon?: boolean; iconSrc?: string; }[]; img: string; diff --git a/frontend-next-migration/src/entities/JoinUs/ui/Block.module.scss b/frontend-next-migration/src/entities/JoinUs/ui/Block.module.scss index 4e62fac50..581b56170 100644 --- a/frontend-next-migration/src/entities/JoinUs/ui/Block.module.scss +++ b/frontend-next-migration/src/entities/JoinUs/ui/Block.module.scss @@ -1,52 +1,67 @@ .Container { + position: relative; + + display: flex; + align-items: center; + justify-content: center; + + width: 100%; + min-height: 240px; margin: auto 0; - padding: 0 0 5px 0; - text-align: center; - height: 100%; - place-content: center; - border-radius: var(--border-radius-custom); + padding: 40px 48px; + + text-align: left; + + border-radius: 12px; background-color: #1E3544; + border: 2px solid black; - box-shadow: 0.5rem 0.5rem black; - text-align: left; - display: flex; - align-items: center; - justify-content: flex-start; + box-shadow: 8px 8px 0 black; + .Content { + width: 100%; - .Content{ - flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; } - + .multilineText { white-space: pre-line; - } + } + + .ImageWrapper { + position: absolute; + + left: 48px; + top: 50%; + + transform: translateY(-50%); - .ImageWrapper { - width: fit-content; - height: 236px; display: flex; - flex-shrink: 0; - margin: auto 10px; - padding: 10px; align-items: center; + justify-content: center; - - img{ - width: 12em; - height: 12em; - object-fit: center; + img { + width: 10em; + height: 10em; + object-fit: contain; } - } + } h2 { - padding: 15px 0 0 0; - margin: 0; + margin: 0 0 16px; + color: var(--primary-color); font: var(--font-sw-xxl); + line-height: 1.1; + white-space: pre-line; + text-align: center; } h3 { @@ -55,139 +70,289 @@ } p { + max-width: 720px; + + margin: 0 0 24px; + font: var(--font-dm-s); - margin: 15px 0; + color: var(--text-color); - line-height: 1.1em; - text-align: left; - max-width: 85%; - align-self: flex-start; - + + line-height: 1.5; + text-align: center; } a { - text-decoration: underline; + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: center; + + gap: 4px; + font: var(--font-dm-s); - color: var(--primary-color)!important; + + color: var(--primary-color) !important; text-decoration: none; - + + white-space: nowrap; &:hover { - color: var(--secondary-color)!important; + color: var(--secondary-color) !important; } - - &::after { - content: ''; - display: block; - padding: 0 0 10px; - } } + .linkWrapper { + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + + column-gap: 24px; + row-gap: 12px; + padding: 0; margin: 0; - line-height: 0.8em; + + line-height: 1.4; + } + + /* text link spacing */ + .linkWrapper:not(:has(.icon)) { + margin: 0 24px; + } + + /* social icon spacing */ + .linkWrapper:has(.icon) { + margin: 0 -12px; } - - .linkWrapper:has(img) { + .socialLinks { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: nowrap; + gap: 4px; + } + .linkWrapper:has(.icon) { display: flex; align-items: center; - margin: 0 10px 0 0; - a { - display:inline-flex; + + margin-top: 0; + margin-bottom: 0; + + a { + display: inline-flex; + align-items: center; flex-direction: row; - img{ - color: #ffa100; - width: 2.7vh; + img:not(.ExternalIcon) { + width: 28px; + height: 28px; + transition: transform 0.15s ease; - + + filter: + brightness(0) + saturate(100%) + invert(70%) + sepia(77%) + saturate(2247%) + hue-rotate(360deg) + brightness(101%) + contrast(105%); } - - &:hover img { - transform: scale(1.1); - } - } + &:hover img:not(.ExternalIcon) { + transform: scale(1.1); + } } + } } +.ExternalIcon { + width: 0.8em; + height: 0.8em; + + display: inline-block; + margin-left: 0.2em; + + vertical-align: middle; + + object-fit: contain; +} + +.Reverse .ImageWrapper { + left: auto; + right: 48px; +} @media (max-width: breakpoint(md)) { .Container { flex-direction: column; align-items: center; - justify-content: center; - text-align: center; - padding: 10px 0 0; - margin: auto; + justify-content: flex-start; + width: 100%; + + min-height: 520px; + + margin: auto; + padding: 56px 24px; + gap: 40px; + + text-align: center; + box-shadow: 0.2rem 0.2rem #121212; + .Content { + width: 100%; + + gap: 40px; + } + + .Content > .linkWrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + row-gap: 20px; + } + .linkWrapper { margin: 0; padding: 0; + + width: auto; } + + .socialLinks { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + + gap: 4px; + + img { + width: 32px !important; + height: 32px !important; + } + } + .ImageWrapper { + position: static; + + transform: none; + width: 100%; height: auto; - margin: 0; - padding: 0; + display: flex; justify-content: center; align-items: center; - img{ + margin: 0; + padding: 0; + + img { width: 8em; height: 8em; - object-fit: center; + object-fit: contain; } } - - - p{ - font: var(--font-dm-xs); - margin: 10px auto; - padding: 0 10px; + h2 { + margin: 0 0 36px; + font-size: clamp(2rem, 6vw, 3.6rem); + } + + p { + max-width: 270px; + + margin: 8px 0 56px; + font-size: clamp(1.4rem, 3.5vw, 1.6rem); + font-size: 1.6rem; + font-weight: 400; + line-height: 1.65; + text-align: center; + } + + a { + display: inline-flex; + flex-direction: column; + align-items: center; + font-size: clamp(1.4rem, 3.5vw, 1.6rem); + justify-content: center; + + font-size: 1.6rem; + font-weight: 400; + line-height: 1.65; + + gap: 2px; + text-align: center; - max-width: 100%; + + white-space: normal; } - .linkWrapper:has(img) { + .ExternalIcon { + display: block; + + width: 0.8em; + height: 0.8em; + + margin-top: 2px; + margin-left: 0; + } + + .linkWrapper:has(.icon) { display: flex; align-items: center; - width: 100%; - margin: 0 10px 10px ; - } + width: auto; + margin: 0; + } } } -@media (min-width: breakpoint(md)) and (max-width: 900px) { + +@media (min-width: breakpoint(md)) and (max-width: 1200px) { + .Container { + padding: 80px 64px 32px; + .ImageWrapper { - width: fit-content; - height: auto; - margin: 0; - padding: 0; - display: flex; - justify-content: center; - align-items: center; + top: 12px; + left: 24px; + right: auto; - img{ - width: 8em; - height: 8em; - object-fit: center; + transform: none; + + img { + width: 5em; + height: 5em; } } } -} - - + .Reverse.Container { + padding: 80px 64px 32px; + .ImageWrapper { + top: 12px; + right: 24px; + left: auto; + transform: none; + img { + width: 5em; + height: 5em; + } + } + } +} \ No newline at end of file diff --git a/frontend-next-migration/src/entities/JoinUs/ui/Block.tsx b/frontend-next-migration/src/entities/JoinUs/ui/Block.tsx index dbbe8d212..9719c24ec 100644 --- a/frontend-next-migration/src/entities/JoinUs/ui/Block.tsx +++ b/frontend-next-migration/src/entities/JoinUs/ui/Block.tsx @@ -1,5 +1,6 @@ import type { BlockSection } from '../types'; import cls from './Block.module.scss'; +import externalLinkIcon from '@/shared/assets/icons/ExternalLink.svg'; /** * Block Component @@ -12,14 +13,24 @@ import cls from './Block.module.scss'; interface Props { block: BlockSection; + reverse?: boolean; } export const Block = (props: Props) => { - const { block } = props; + const { block, reverse = false } = props; + + if (!block) { + return null; + } + + const links = block.links ?? []; + + const socialLinks = links.filter((link) => link.iconSrc); + const normalLinks = links.filter((link) => !link.iconSrc); return (
@@ -30,29 +41,77 @@ export const Block = (props: Props) => { alt={block.imgAlt} />
+

{block.label}

+

{block.description}

+
- {block.links.map((link, index) => ( + {normalLinks.slice(0, 1).map((link, index) => (
- {link.iconSrc && ( + {link.text} + + {link.showExternalIcon && ( // eslint-disable-next-line @next/next/no-img-element + External link + )} + +
+ ))} + + {socialLinks.length > 0 && ( +
+ {socialLinks.map((link, index) => ( + + {/* eslint-disable-next-line @next/next/no-img-element */} {`${link.text} - )} + + ))} +
+ )} + + {normalLinks.slice(1).map((link, index) => ( +
+ {link.text} + + {link.showExternalIcon && ( + // eslint-disable-next-line @next/next/no-img-element + External link + )}
))} diff --git a/frontend-next-migration/src/preparedPages/ClanPages/ui/ClanAllSubPage/ClanAllSubPage.tsx b/frontend-next-migration/src/preparedPages/ClanPages/ui/ClanAllSubPage/ClanAllSubPage.tsx index 80c2ff285..479369533 100644 --- a/frontend-next-migration/src/preparedPages/ClanPages/ui/ClanAllSubPage/ClanAllSubPage.tsx +++ b/frontend-next-migration/src/preparedPages/ClanPages/ui/ClanAllSubPage/ClanAllSubPage.tsx @@ -14,10 +14,11 @@ import mobileCardCls from '@/shared/ui/v2/MobileCard/ui/MobileCard.module.scss'; import { PageTitle } from '@/shared/ui/PageTitle'; import { SearchBar } from '../ClanLayout/ClanLayout'; import cls from './ClanAllSubPage.module.scss'; -import clanLogo from '@/shared/assets/images/clanLogos/CommonSelectHeart 1.png'; +import clanLogo from '@/shared/assets/images/clanLogos/ClanLogo_Placeholder.png'; import iconLeaderboard from '@/shared/assets/images/clanLogos/LeaderboardWinFirstPlace.png'; -import iconFlagFi from '@/shared/assets/images/clanLogos/CommonFlagFinland 1.png'; -import starGray from '@/shared/assets/images/clanLogos/TopPanelMatchmakingPorvarit.png'; +import iconFlagFi from '@/shared/assets/images/clanLogos/CommonFlagFinland.png'; +import iconCommonLockOpen from '@/shared/assets/images/clanLogos/CommonLockOpen.png'; +import iconClanAgeTeenages from '@/shared/assets/images/clanLogos/ClanAgeTeenages.png'; type ClanItem = GetClansResponse['data']['Clan'][number]; @@ -118,11 +119,6 @@ const ClansViewMobile = ({ clans }: MobileProps) => { className={cls.MobileCardItem} > - - { />
- score - - {clan.gameCoins} + + leaderboard + lock + teenage + flag
-
- {t('members')} {clan.playerCount} / 30 +
+

+ {t('members')} {clan.playerCount} / 30 +

+

+ {t('position')} {clan.positionLeaderboard ?? '-'} +

+
+ +
+ {clan.labels?.slice(0, 3).map((label) => ( + + {label} + + ))}
@@ -189,20 +218,29 @@ const ClansViewDesktop = ({ clans, onClickToClan }: DesktopProps) => { alt="leaderboard" className={cardCls.ClanInfoIcon} /> + lock + teenage flag - - - - {t('members')} {clan.playerCount} / 30 - - - {clan.gameCoins} - +
+
+ + {t('members')} {clan.playerCount} / 30 + + + {t('position')} {clan.positionLeaderboard ?? '-'}
@@ -218,7 +256,6 @@ const ClansViewDesktop = ({ clans, onClickToClan }: DesktopProps) => { alt={label} className={cardCls.ClanLabelIcon} /> - {label} ))}
diff --git a/frontend-next-migration/src/preparedPages/JoinUsPage/types/index.ts b/frontend-next-migration/src/preparedPages/JoinUsPage/types/index.ts index 98ae16680..a3987381e 100644 --- a/frontend-next-migration/src/preparedPages/JoinUsPage/types/index.ts +++ b/frontend-next-migration/src/preparedPages/JoinUsPage/types/index.ts @@ -16,6 +16,7 @@ export interface BlockSection { text: string; url: string; isExternal?: boolean; + showExternalIcon?: boolean; iconSrc?: string; type?: string; // 'mail' for mailto, 'phone' for number, 'link' for normal links clickable?: boolean; // Set to false for numbers that should not be clickable diff --git a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.module.scss b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.module.scss index 73a426a2c..1cda279ec 100644 --- a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.module.scss +++ b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.module.scss @@ -1,65 +1,54 @@ .Container { + width: 100%; + max-width: 1440px; + + margin: 0 auto; + padding: 0 48px 64px; border-radius: var(--border-radius-custom); - padding: 0 20px 20px 0; h1 { - font: var(--font-sw-5xl); - line-height: 1.5rem; - text-align: left; - color: var(--primary-color); - } + margin: 64px 0 40px; - @keyframes lineExpand { - 0% { - width: 0; - } + color: var(--primary-color); - 100% { - width: 500px; - max-width: 70vw; - } + font: var(--font-sw-5xl); + line-height: 1.1; + text-align: left; } } @media screen and (max-width: breakpoint(md)) { .Container { - padding: 10px; - margin: 0 auto; + padding: 96px 16px 48px; h1 { + margin: 32px 0 24px; + font: var(--font-sw-xxl); - margin: 0 0 20px; - margin-top: 7vh; - padding: 20px 0 0 0; - text-align: center; + text-align: left; } } - } @media screen and ((min-width: breakpoint(sm)) and (max-width: breakpoint(md))) { .Container { - padding: 10px 20px; - margin: 0 auto; + padding: 96px 24px 56px; h1 { - font: var(--font-sw-5xl); - margin: 40px auto; - margin-top: 10vh; + margin: 48px 0 32px; + + font: var(--font-sw-4xl); } } } @media screen and (min-width: breakpoint(md)) { .Container { - padding: 10px 20px; - margin: 0 auto; + padding: 0 48px 64px; h1 { - font: var(--font-sw-5xl); - margin: 40px auto; - margin-top: 10vh; + margin-top: 64px; } } } \ No newline at end of file diff --git a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.stories.tsx b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.stories.tsx index 17f89e5ca..d607d9229 100644 --- a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.stories.tsx +++ b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.stories.tsx @@ -127,13 +127,10 @@ type Story = StoryObj; export const Default: Story = { args: { title: 'Join Us!', - discordBlock: mockDiscordBlock, - // redditBlock: mockRedditBlock, - teachersBlock: mockTeachersBlock, + getInTouchAndFollowBlock: mockConnectionBlock, + communityAndOpportunitiesBlock: mockDiscordBlock, + educationProfessionalsBlock: mockTeachersBlock, feedbackBlock: mockFeedbackBlock, - duunitoriBlock: mockDuunitoriBlock, - connectionBlock: mockConnectionBlock, - instagramBlock: mockInstagramBlock, }, parameters: { layout: 'fullscreen', diff --git a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.test.tsx b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.test.tsx index 15c079f8b..4732a9bc5 100644 --- a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.test.tsx +++ b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.test.tsx @@ -34,31 +34,15 @@ const feedbackBlock: BlockSection = { img: '', }; -const duunitoriBlock: BlockSection = { - label: 'Duunitori', - description: 'Open positions at Duunitori.', - links: [{ text: 'Duunitori link', url: 'https://example.com', isExternal: true }], - img: '', -}; - -const instagramBlock: BlockSection = { - label: 'Instagram', - description: 'Follow us on Instagram!', - links: [{ text: 'Instagram link', url: 'https://example.com', isExternal: true }], - img: '', -}; - describe('JoinUsPage', () => { const setup = () => render( , ); @@ -73,31 +57,24 @@ describe('JoinUsPage', () => { // Labels expect(screen.getByText('Discord')).toBeInTheDocument(); expect(screen.getByText('Reddit')).toBeInTheDocument(); - expect(screen.getByText('Instagram')).toBeInTheDocument(); expect(screen.getByText('Teachers!')).toBeInTheDocument(); expect(screen.getByText('Feedback')).toBeInTheDocument(); - expect(screen.getByText('Duunitori')).toBeInTheDocument(); // Descriptions expect(screen.getByText('Join our Discord server to connect!')).toBeInTheDocument(); expect(screen.getByText('Reddit community page.')).toBeInTheDocument(); - expect(screen.getByText('Follow us on Instagram!')).toBeInTheDocument(); expect(screen.getByText('Information for teachers.')).toBeInTheDocument(); expect(screen.getByText('Send us your feedback.')).toBeInTheDocument(); - expect(screen.getByText('Open positions at Duunitori.')).toBeInTheDocument(); // Link texts expect(screen.getByText('Discord link')).toBeInTheDocument(); expect(screen.getByText('Reddit link')).toBeInTheDocument(); - expect(screen.getByText('Instagram link')).toBeInTheDocument(); expect(screen.getByText('Teachers link')).toBeInTheDocument(); expect(screen.getByText('Feedback link')).toBeInTheDocument(); - expect(screen.getByText('Duunitori link')).toBeInTheDocument(); // imgs expect(screen.queryByAltText('Discord Icon')).not.toBeInTheDocument(); expect(screen.queryByAltText('Reddit Icon')).not.toBeInTheDocument(); - expect(screen.queryByAltText('Instagram Icon')).not.toBeInTheDocument(); expect(screen.queryByAltText('Teachers Icon')).not.toBeInTheDocument(); expect(screen.queryByAltText('Feedback Icon')).not.toBeInTheDocument(); }); diff --git a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.tsx b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.tsx index 299a7a258..de4f16a2f 100644 --- a/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.tsx +++ b/frontend-next-migration/src/preparedPages/JoinUsPage/ui/JoinUsPage.tsx @@ -5,24 +5,20 @@ import { BlockSection } from '../types'; export interface Props { title: string; - discordBlock: BlockSection; - connectionBlock: BlockSection; - instagramBlock: BlockSection; - teachersBlock: BlockSection; + getInTouchAndFollowBlock: BlockSection; + communityAndOpportunitiesBlock: BlockSection; + educationProfessionalsBlock: BlockSection; feedbackBlock: BlockSection; - duunitoriBlock: BlockSection; navHeight?: number; } export const JoinUsPage = (props: Props) => { const { title, - discordBlock, - connectionBlock, - instagramBlock, - teachersBlock, + getInTouchAndFollowBlock, + communityAndOpportunitiesBlock, + educationProfessionalsBlock, feedbackBlock, - duunitoriBlock, } = props; return ( @@ -31,13 +27,12 @@ export const JoinUsPage = (props: Props) => {

{title}

+ diff --git a/frontend-next-migration/src/preparedPages/MainPage/ui/MainPage.tsx b/frontend-next-migration/src/preparedPages/MainPage/ui/MainPage.tsx index 7650144e0..4c832329c 100644 --- a/frontend-next-migration/src/preparedPages/MainPage/ui/MainPage.tsx +++ b/frontend-next-migration/src/preparedPages/MainPage/ui/MainPage.tsx @@ -25,6 +25,11 @@ import { ContactSection, ContactSectionProps } from './_components/sections/Cont import { useParams } from 'next/navigation'; import { useClientTranslation } from '@/shared/i18n'; import { WallIntroAnimation } from '@/shared/ui/v2/WallIntroAnimation'; +import { CardV2 } from '@/shared/ui/v2/CardV2'; +import char1 from '@/shared/assets/images/mainpage/Defencegallery202.png'; +import char2 from '@/shared/assets/images/mainpage/Defencegallery403.png'; +import char3 from '@/shared/assets/images/mainpage/Defencegallery603.png'; +import { Button, ButtonTheme } from '@/shared/ui/v2/Button'; export type Props = { projectDescription: ProjectDescriptionProps; @@ -51,87 +56,60 @@ function MainPage(props: Props) {
-
+
+
+ + + {t('descriptionCard-button')} + + } + /> + - +
+

{t('newsSection-title')}

+
+ {groupedNews.map((news) => { + const imageSrc = news.titlePicture?.id + ? `${directusBaseUrl}/assets/${news.titlePicture.id}` + : hannu.src; - + return ( + + ); + })} +
- {isMobileSize ? ( - - ) : ( -
- - - - {t('descriptionCard-title')} - - - - - - -
- )} - - - -
-

{t('newsSection-title')}

-
- {groupedNews.map((news) => { - const imageSrc = news.titlePicture?.id - ? `${directusBaseUrl}/assets/${news.titlePicture.id}` - : hannu.src; - - return ( - - ); - })} -
- -
- -
); } diff --git a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/AltZone/AltZone.module.scss b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/AltZone/AltZone.module.scss index 3f91a8f98..a2e0f2e5b 100644 --- a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/AltZone/AltZone.module.scss +++ b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/AltZone/AltZone.module.scss @@ -131,7 +131,7 @@ flex-direction: column-reverse; justify-content: center; align-items: center; - width: 80%; + width: 70%; } .title { font-size: 1.7rem; diff --git a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.module.scss b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.module.scss index 51cc8bb33..43ad1ff22 100644 --- a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.module.scss +++ b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.module.scss @@ -2,4 +2,20 @@ width: 24px; height: 24px; flex-shrink: 0; -} \ No newline at end of file +} +.DiscordButton{ + border-color: var(--primary-color); + box-shadow: black; +} +.ContactSection { + min-height: 401px; + display: flex; + align-items: center; + + @media (max-width: breakpoint(md)) { + padding: 2rem 1rem; + min-height: unset; + } +} + + diff --git a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.tsx b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.tsx index 8e408a439..a4823c65e 100644 --- a/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.tsx +++ b/frontend-next-migration/src/preparedPages/MainPage/ui/_components/sections/ContactSection/ContactSection.tsx @@ -3,7 +3,7 @@ import { CTASection } from '@/shared/ui/CtaSection'; import { Button, ButtonTheme } from '@/shared/ui/v2/Button'; import Image from 'next/image'; import DiscordIcon from '@/shared/assets/images/Discord2.svg'; -import ContactImg from '@/shared/assets/images/Orang_hero.webp'; +import ContactImg from '@/shared/assets/images/mainpage/Defencegallery401.png'; import cls from './ContactSection.module.scss'; export type ContactLink = { @@ -29,6 +29,7 @@ export const ContactSection = (props: ContactSectionProps) => { path={link.link} isExternal={true} theme={isDiscord ? ButtonTheme.OUTLINE : ButtonTheme.PRIMARY} + className={isDiscord ? cls.DiscordButton : undefined} > {link.text} {isDiscord && ( @@ -46,6 +47,8 @@ export const ContactSection = (props: ContactSectionProps) => { return ( { actions, className = '', mobileButtonLayout = 'column', + titleClassName = '', } = props; const mods: Record = { @@ -67,7 +69,7 @@ export const CTASection = (props: CTASectionProps) => {
-

{title}

+

{title}

{description &&
{description}
} {extraText &&
{extraText}
} {actions &&
{actions}
} diff --git a/frontend-next-migration/src/shared/ui/Glass/index.ts b/frontend-next-migration/src/shared/ui/Glass/index.ts new file mode 100644 index 000000000..3426eff34 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/Glass/index.ts @@ -0,0 +1,2 @@ +export { Glass } from './ui/Glass'; +export type { GlassProps } from './ui/Glass'; diff --git a/frontend-next-migration/src/shared/ui/Glass/ui/Glass.module.scss b/frontend-next-migration/src/shared/ui/Glass/ui/Glass.module.scss new file mode 100644 index 000000000..e0cc599e6 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/Glass/ui/Glass.module.scss @@ -0,0 +1,87 @@ +.Glass { + position: relative; + display: block; + width: 100%; + + isolation: isolate; + + > * { + position: relative; + z-index: 1; + } + + /* BLUR CORE */ + &::before { + content: ''; + position: absolute; + + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 55%; + height: 45%; + + z-index: 0; + + backdrop-filter: blur(2px); + -webkit-backdrop-filter: blur(2px); + + background: transparent; + } + + /* CLOUD */ + &::after { + content: ''; + position: absolute; + + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 120%; + height: 100%; + + z-index: 0; + + background: + /* base */ + radial-gradient( + ellipse at center, + rgba(23, 41, 54, 0.7) 0%, + rgba(23, 41, 54, 0.6) 50%, + rgba(23, 41, 54, 0.45) 75%, + rgba(23, 41, 54, 0.25) 90%, + rgba(23, 41, 54, 0) 100% + ), + + /* cloud 1 */ + radial-gradient( + 55% 50% at 35% 50%, + rgba(23, 41, 54, 0.5) 0%, + rgba(23, 41, 54, 0.4) 60%, + rgba(23, 41, 54, 0.25) 85%, + rgba(23, 41, 54, 0) 100% + ), + + /* cloud 2 */ + radial-gradient( + 55% 50% at 65% 55%, + rgba(23, 41, 54, 0.48) 0%, + rgba(23, 41, 54, 0.38) 60%, + rgba(23, 41, 54, 0.25) 85%, + rgba(23, 41, 54, 0) 100% + ), + + /* cloud 3 */ + radial-gradient( + 50% 45% at 50% 75%, + rgba(23, 41, 54, 0.45) 0%, + rgba(23, 41, 54, 0.35) 60%, + rgba(23, 41, 54, 0.25) 85%, + rgba(23, 41, 54, 0) 100% + ); + + filter: blur(18px); + } +} \ No newline at end of file diff --git a/frontend-next-migration/src/shared/ui/Glass/ui/Glass.tsx b/frontend-next-migration/src/shared/ui/Glass/ui/Glass.tsx new file mode 100644 index 000000000..b4cd84af5 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/Glass/ui/Glass.tsx @@ -0,0 +1,27 @@ +import { type ReactNode, ElementType } from 'react'; +import clsx from 'clsx'; +import styles from './Glass.module.scss'; + +export type GlassProps = { + as?: T; + children: ReactNode; + className?: string; +} & Omit, 'as' | 'children' | 'className'>; + +export const Glass = ({ + as, + children, + className, + ...props +}: GlassProps) => { + const Component = as || 'div'; + + return ( + + {children} + + ); +}; diff --git a/frontend-next-migration/src/shared/ui/v2/CardV2/index.ts b/frontend-next-migration/src/shared/ui/v2/CardV2/index.ts new file mode 100644 index 000000000..7b7f5771d --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/CardV2/index.ts @@ -0,0 +1,2 @@ +export { default as CardV2 } from './ui/Cardv2'; +export type { Props as CardV2Props } from './ui/Cardv2'; diff --git a/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.module.scss b/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.module.scss new file mode 100644 index 000000000..ee9599f3d --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.module.scss @@ -0,0 +1,155 @@ +.CardV2 { + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + background-color: transparent; + padding: clamp(24px, 6.77vw, 130px); + opacity: 0; + transform: translateY(20px); + transition: opacity 0.8s ease, transform 0.8s ease; + + &.inView { + opacity: 1; + transform: translateY(0); + } + @media (max-width: breakpoint(md)) { + padding: 0; +} +} + +.Inner { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 0px; + gap: clamp(1.5rem, 5vw, 6rem); + +@media (max-width: breakpoint(md)) { + flex-direction: column-reverse; + padding: 0 16px 24px; + gap: 8px; + align-items: center; + text-align: center; +} +} + +.ImagesBlock { + position: relative; + flex-shrink: 0; + width: clamp(328px, 28.125vw, 540px); + height: clamp(233px, 17.24vw, 331px); + +@media (max-width: breakpoint(md)) { + width: 328px; + height: 233px; + margin: 0 auto; +} +} + +.ImgWrapper { + position: absolute; + bottom: 0; + width: 40.19%; + height: 90%; + + &:nth-child(1) { + left: 0; + width: 52.96%; + z-index: 1; + } + + &:nth-child(2) { + left: 25.93%; + width: 58.33%; + z-index: 3; + } + + &:nth-child(3) { + left: 57.96%; + width: 53.52%; + z-index: 2; + } + +@media (max-width: breakpoint(md)) { + height: auto; + + &:nth-child(1) { + left: -4px; + width: 168px; + height: 168px; + top: 10px; + bottom: auto; + } + &:nth-child(2) { + left: 70px; + width: 196px; + height: 196px; + top: 37px; + bottom: auto; + } + &:nth-child(3) { + left: 177px; + width: 178px; + height: 178px; + top: 0px; + bottom: auto; + } +} +} + +.TextBlock { + display: flex; + flex-direction: column; + gap: 20px; + flex: 1; + min-width: 0; + + @media (max-width: breakpoint(md)) { + width: 100%; + align-items: center; + padding: 0; + } +} + +.Title { + font: var(--font-sw-5xl); + font-size: clamp(var(--font-size-xl), 3.33vw, var(--font-size-5xl)); + line-height: 1; + letter-spacing: -0.04em; + color: var(--primary-color); + margin: 0; + + @media (max-width: breakpoint(md)) { + font: var(--font-sw-xl); + line-height: 43px; + letter-spacing: -0.04em; + } +} + +.Description { + font: var(--font-dm-l); + font-size: clamp(var(--font-size-m), 1.25vw, var(--font-size-l)); + color: var(--white); + margin: 0; + + @media (max-width: breakpoint(md)) { + font: var(--font-dm-m); + line-height: 21px; + letter-spacing: -0.04em; + text-align: center; + } +} + +.Actions { + display: flex; + gap: 12px; + flex-wrap: wrap; + margin-top: 4px; + + @media (max-width: breakpoint(md)) { + justify-content: center; + } +} \ No newline at end of file diff --git a/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.tsx b/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.tsx new file mode 100644 index 000000000..e3199d752 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/CardV2/ui/Cardv2.tsx @@ -0,0 +1,88 @@ +'use client'; +import { ReactNode } from 'react'; +import Image, { StaticImageData } from 'next/image'; +import { useInView } from 'react-intersection-observer'; +import { classNames } from '@/shared/lib/classNames/classNames'; +import cls from './Cardv2.module.scss'; +import { Glass } from '@/shared/ui/Glass'; +import { Container } from '@/shared/ui/Container'; + +/** + * Props for CardV2. + * + * @property {Array} images - the character images shown on the left + * @property {string} title - the big title text + * @property {string} [description] - smaller text under the title + * @property {ReactNode} [actions] - buttons or links, optional + * @property {string} [className] - extra css class if needed + */ +export type Props = { + images: { src: StaticImageData | string; alt?: string }[]; + title: string; + description?: string; + actions?: ReactNode; + className?: string; +}; + +/** + * cardV2 is a section that shows characters on the left and text on the right. + * on mobile the characters go below the text. + * + * @param {Props} props - the stuff passed into the component + * @returns {JSX.Element} the section element + * + * @example + * ```tsx + * Tutustu kaikkiin hahmoihin} + * /> + * ``` + */ +const CardV2 = (props: Props) => { + const { images, title, description, actions, className } = props; + + const { ref, inView } = useInView({ + rootMargin: '-100px 0px', + triggerOnce: true, + }); + + return ( +
+ +
+ {images.map((img, i) => ( +
+ {img.alt +
+ ))} +
+ + +

{title}

+ {description &&

{description}

} + {actions &&
{actions}
} +
+
+
+ ); +}; + +export default CardV2; diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.module.scss b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.module.scss new file mode 100644 index 000000000..74dfbfaf3 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.module.scss @@ -0,0 +1,81 @@ +/* Contact section of the footer, containing title, email addresses and "Work with us" link. */ + +.FooterContact { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + max-width: clamp(19.375rem, 16vw, 28rem); + gap: clamp(0.9375rem, 0.8vw, 1.5rem); + text-align: center; +} + +.Title { + margin: 0; + font-family: var(--font-family-secondary); + font-weight: 700; + line-height: 1; + color: #ffa100; + font-size: clamp(1.5rem, 1.15vw, 2rem); +} + +.EmailLabel { + margin: 0; + color: #faf9f6; + font-family: var(--font-family-secondary); + font-weight: 400; + line-height: 1; + text-decoration: underline; + text-decoration-style: solid; + font-size: clamp(1.25rem, 0.95vw, 1.625rem); +} + +.EmailList { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; +} + +.Email, +.WorkWithUsLink { + color: #faf9f6; + text-decoration: none; + font: var(--font-dm-s); + font-style: normal; + transition: + color 0.2s ease, + opacity 0.2s ease; + + &:hover { + color: var(--footer-accent, var(--primary-color)); + opacity: 0.8; + } + + &:focus { + outline: 2px solid var(--footer-accent, var(--primary-color)); + outline-offset: 2px; + } +} + +.WorkWithUsLink { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.25rem; +} + +.Email { + text-decoration: underline; + text-underline-offset: 2px; +} + +.ExternalIcon { + width: 0.65rem; + height: 0.65rem; + color: var(--footer-accent, var(--primary-color)); +} diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.tsx b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.tsx new file mode 100644 index 000000000..b64cc5c83 --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/FooterContact.tsx @@ -0,0 +1,61 @@ +import { memo } from 'react'; +import { classNames } from '@/shared/lib/classNames/classNames'; +import { AppLink } from '@/shared/ui/AppLink/AppLink'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faExternalLink } from '@fortawesome/free-solid-svg-icons'; +import cls from './FooterContact.module.scss'; + +/** Props for the FooterContact component, which displays contact information in the footer. */ +interface FooterContactProps { + className?: string; + title: string; + emailLabel?: string; + emails: string[]; + workWithUsLabel?: string; +} + +const DUUNITORI_JOBS_URL = 'https://duunitori.fi/tyopaikat?haku=Psyche%27s%20Royale%20Gaming'; + +/** Component for displaying contact information in the footer. + * @param props - Props for the FooterContact component, including title, optional email label, list of email addresses, and an optional "Work with us" label. + * @returns A React component that renders the footer contact section with the provided information. + * @remarks This component is memoized for performance optimization, preventing unnecessary re-renders when props do not change. + */ +export const FooterContact = memo((props: FooterContactProps) => { + const { className = '', title, emailLabel, emails, workWithUsLabel } = props; + + /* Render the footer contact section with a title, optional email label, list of email addresses, and a "Work with us" link. */ + return ( +
+

{title}

+ {emailLabel &&

{emailLabel}

} + + {workWithUsLabel && ( + + {workWithUsLabel} + + + )} +
+ ); +}); + +FooterContact.displayName = 'FooterContact'; diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/index.ts b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/index.ts new file mode 100644 index 000000000..2af983bec --- /dev/null +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterContact/index.ts @@ -0,0 +1 @@ +export { FooterContact } from './FooterContact'; diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.module.scss b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.module.scss index 09b0b8d91..0b5d38e4b 100644 --- a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.module.scss +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.module.scss @@ -1,57 +1,141 @@ .Footer { - font: var(--font-dm-xl); - background: var(--background); + --footer-bg: #1e3544; + --footer-bar-bg: #00000026; + --footer-accent: #ffa100; + --footer-text: #faf9f6; + --footer-muted: #faf9f6; + --footer-layout-width: min(100%, 120rem); + --footer-column-padding: clamp(2.5rem, 3.33vw, 4rem); + --footer-logo-width: clamp(12rem, 14.32vw, 17.1875rem); + --footer-logo-height: clamp(11.75rem, 14.06vw, 16.875rem); + --footer-social-icon-size: 2.5rem; + --footer-social-gap: 1.8125rem; + --footer-social-bar-height: 6.25rem; + --footer-copyright-size: 0.875rem; + + font-family: var(--font-dm-s); + background: var(--footer-bg); + color: var(--footer-text); +} + +.SocialBar { display: flex; - flex-direction: row; - justify-content: space-evenly; + justify-content: center; align-items: center; - padding: 5px 25px 5px 25px; - position: relative; - z-index: 20; + min-height: var(--footer-social-bar-height); + padding-block: 1.25rem; + background: var(--footer-bar-bg); + box-sizing: border-box; } -.title { - padding-top: 1rem; - color: var(--primary-color); - text-align: center; - font: var(--font-sw-xl); +.socialSection { + display: flex; + justify-content: center; + gap: var(--footer-social-gap); + margin: 0; } -.socialSection { - padding-top: 1rem; +.socialSection > * { + width: var(--footer-social-icon-size); + height: var(--footer-social-icon-size); + border-radius: 0.625rem; +} - /* Footer controls layout now (was previously inside SocialSection component) */ +.socialSection img { + width: var(--footer-social-icon-size); + height: var(--footer-social-icon-size); + border-radius: 0.625rem; +} + +.Inner { display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: stretch; + justify-items: stretch; + width: var(--footer-layout-width); + margin: 0 auto; + min-height: max(var(--footer-logo-height), 17rem); +} + +.Column { + display: flex; align-items: center; - justify-items: center; + justify-content: center; + min-width: 0; + padding: var(--footer-column-padding); + box-sizing: border-box; +} - margin: 0 auto; - max-width: 600px; +.BrandColumn { + justify-content: center; +} + +.Logo { + display: block; + flex: 0 0 auto; + width: var(--footer-logo-width); + height: var(--footer-logo-height); + object-fit: cover; + border-radius: 50%; + transform: rotate(0deg); +} - grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); +.contactSection, +.infoSection { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; } -.rights { - padding: 1rem 0; +.BottomSection { + display: flex; + justify-content: center; + width: 100%; + padding-bottom: 0.75rem; +} + +.Copyright { + margin: 0; + color: #ffffff; + font: var(--font-dm-s); + font-size: var(--footer-copyright-size); + line-height: 1; text-align: center; } -.FooterContainer { - align-self: flex-start; - justify-items: center; +.CopySymbol { + margin-right: 0.25rem; } -.ImageContainer { - justify-items: flex-start; +@media (max-width: 1279px) { + .Footer { + --footer-column-padding: clamp(2rem, 3vw, 3rem); + --footer-logo-width: clamp(10rem, 18vw, 14rem); + --footer-logo-height: clamp(9.75rem, 17.67vw, 13.75rem); + } } -.FeedbackContainer { - display: flex; - justify-content: flex-end; +@media (min-width: 1920px) { + .Footer { + --footer-layout-width: min(100%, 160rem); + --footer-column-padding: clamp(4rem, 4vw, 8rem); + --footer-logo-width: clamp(17.1875rem, 10vw, 24rem); + --footer-logo-height: clamp(16.875rem, 9.82vw, 23.5rem); + --footer-social-icon-size: clamp(2.5rem, 1.5vw, 3.5rem); + --footer-social-gap: clamp(1.8125rem, 1.5vw, 3rem); + --footer-social-bar-height: clamp(6.25rem, 4vw, 8rem); + --footer-copyright-size: clamp(0.875rem, 0.65vw, 1.25rem); + } } @media (max-width: 1023px) { - .socialSection { - grid-template-columns: repeat(auto-fit, minmax(85px, 1fr)); + .Footer { + --footer-column-padding: 2rem; } -} \ No newline at end of file + + .Inner { + grid-template-columns: 1fr; + } +} diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.test.tsx b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.test.tsx index b26c34537..15137fe92 100644 --- a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.test.tsx +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.test.tsx @@ -2,9 +2,16 @@ import { render, screen } from '@testing-library/react'; import { SocialIconLink, Texts } from '@/shared/types/types'; import FooterDesktop from './FooterDesktop'; -jest.mock('@/shared/ui/v2/Feedback'); +jest.mock('next/image', () => ({ + __esModule: true, + default: ({ priority, alt, ...props }: any) => ( + {alt} + ), +})); -// Mock data for social links and texts const mockSocialLinks: SocialIconLink[] = [ { link: 'https://facebook.com', icon: '/icons/facebook.svg', name: 'Facebook' }, { link: 'https://twitter.com', icon: '/icons/twitter.svg', name: 'Twitter' }, @@ -19,47 +26,64 @@ const mockTexts: Texts = { companyName: 'My Company', }; -describe('FooterDesktop', () => { - it('renders without crashing and displays the title', () => { - const title = 'Footer Title'; +const mockFooterLinks = { + workWithUsLabel: 'Apply to us', + whatIsPrgLabel: 'What is PRG', + altZoneHistoryLabel: 'ALT Zone history', + developersDesignersLabel: 'Developers and designers', + termsAndPrivacyLabel: 'Terms and privacy policy', +}; +describe('FooterDesktop', () => { + it('renders the logo, contact section, and info links', () => { render( , ); - // Check if the title is displayed correctly - expect(screen.getByText(title)).toBeInTheDocument(); + expect(screen.getByAltText('PRG Logo')).toBeInTheDocument(); + expect(screen.getByText('Contact information')).toBeInTheDocument(); + expect(screen.getByText('Email addresses')).toBeInTheDocument(); + expect(screen.getByText('hello@example.com')).toBeInTheDocument(); + expect(screen.getByText('Information')).toBeInTheDocument(); + expect(screen.getByText('Apply to us')).toBeInTheDocument(); }); it('renders the SocialSection with correct social links', () => { render( , ); - // Check if the social links are rendered mockSocialLinks.forEach((link) => { - expect(screen.getByAltText(link.name)).toBeInTheDocument(); // Check for images using alt text + expect(screen.getByAltText(link.name)).toBeInTheDocument(); }); }); - it('renders the Rights component with correct texts', () => { + it('renders copyright with the current company', () => { render( , ); - // Check if the Rights component displays the correct year and company name expect( screen.getByText(`${mockTexts.currentYear} ${mockTexts.companyName}`), ).toBeInTheDocument(); diff --git a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.tsx b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.tsx index 2edb38146..4e3ecf780 100644 --- a/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.tsx +++ b/frontend-next-migration/src/shared/ui/v2/Footer/ui/FooterDesktop/FooterDesktop.tsx @@ -1,46 +1,91 @@ import { memo } from 'react'; -import { Container } from '@/shared/ui/Container'; +import Image from 'next/image'; import { SocialIconLink, Texts } from '@/shared/types/types'; -import { Rights } from '@/widgets/Footer/ui/Rights/Rights'; import { SocialSection } from '@/shared/SocialSection/SocialSection'; -import { Title } from '@/widgets/Footer/ui/Title/Title'; +import { FooterContact } from '@/shared/ui/v2/Footer/ui/FooterContact'; +import { FooterInfo } from '@/shared/ui/v2/Footer/ui/FooterInfo'; import cls from './FooterDesktop.module.scss'; import { classNames } from '@/shared/lib/classNames/classNames'; -import { FeedbackCard } from '@/shared/ui/v2/Feedback'; -import { RandomCharacter } from '@/shared/ui/v2/Footer/ui/FooterDesktop/RandomCharacter'; +import prgLogo from '@/shared/assets/images/PRG_Logo.png'; interface Props { - title: string; socialIconLinks: SocialIconLink[]; texts: Texts; + contactTitle: string; + contactEmailLabel?: string; + contactEmails: string[]; + infoTitle: string; + infoLinks?: { + workWithUsLabel: string; + whatIsPrgLabel: string; + altZoneHistoryLabel: string; + developersDesignersLabel: string; + termsAndPrivacyLabel: string; + }; className?: string; } const FooterDesktopComponent = memo((props: Props) => { - const { title, socialIconLinks, texts, className = '' } = props; + const { + socialIconLinks, + texts, + contactTitle, + contactEmailLabel, + contactEmails, + infoTitle, + infoLinks, + className = '', + } = props; return (