Skip to content
Open
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
8 changes: 8 additions & 0 deletions dev-data/hero-page.data.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import coursesPageHeroImg from '@/shared/assets/mentor-with-his-students.webp';
import welcome from '@/shared/assets/welcome.webp';

export const heroPageData = {
school: {
Expand Down Expand Up @@ -26,4 +27,11 @@ export const heroPageData = {
subTitle: ['By teaching others, you learn yourself'],
imageAltText: '',
},
merch: {
mainTitle: 'Merch',
widgetTitle: 'Free assets for your design',
subTitle: [''],
heroImageSrc: welcome,
imageAltText: 'A sloth mascot with arms raised under a welcome sign',
},
};
1,789 changes: 1,237 additions & 552 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"react-double-marquee": "^1.1.0",
"react-markdown": "^10.1.0",
"react-responsive-carousel": "^3.2.23",
"react-swipeable": "^7.0.2",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason we're using react-swipeable here instead of Swiper, given that Swiper is already listed in the dependencies?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gbogdanova good point, since we also have the following issue: #877

"react-youtube": "^10.1.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-slug": "^6.0.0",
Expand All @@ -46,7 +47,8 @@
"remark-rehype": "^11.1.2",
"remark-remove-comments": "^1.1.1",
"remark-toc": "^9.0.0",
"swiper": "^11.2.6"
"swiper": "^11.2.6",
"uuid": "^11.1.0"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand All @@ -64,6 +66,7 @@
"@types/node": "^22.15.3",
"@types/react": "19.1.2",
"@types/react-dom": "19.1.3",
"@types/uuid": "^10.0.0",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This library was used in the 786 branch but it has now been removed. Try pulling the changes from branch 786 again, so that you have the current version and don't add unnecessary dependencies

"@vitejs/plugin-react": "^4.4.1",
"@vitest/coverage-v8": "^3.1.2",
"@vitest/eslint-plugin": "^1.1.44",
Expand Down
13 changes: 13 additions & 0 deletions src/app/merch/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Metadata } from 'next';

import { Merch } from '@/views/merch/merch';

export async function generateMetadata(): Promise<Metadata> {
const title = 'Merch Β· The Rolling Scopes School';

return { title };
}

export default function CommunityRoute() {
return <Merch />;
}
5 changes: 5 additions & 0 deletions src/core/api/app-api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MerchApi } from '@/entities/merch/api/merch-api';
import { TrainerApi } from '@/entities/trainer/api/trainer-api';
import { ApiBaseClass } from '@/shared/api/api-base-class';
import { ApiServices } from '@/shared/types';
Expand All @@ -7,9 +8,13 @@ export class Api {

public readonly trainer: TrainerApi;

public readonly merch: MerchApi;

constructor(private readonly baseURI: string) {
this.services = { rest: new ApiBaseClass(this.baseURI) };

this.trainer = new TrainerApi(this.services);

this.merch = new MerchApi(this.services);
}
}
1 change: 1 addition & 0 deletions src/core/styles/_constants.scss
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ $opacity-100: 1;
$opacity-80: 0.8;
$opacity-70: 0.7;
$opacity-50: 0.5;
$opacity-40: 0.4;
$opacity-30: 0.3;
$opacity-20: 0.2;
$opacity-15: 0.15;
Expand Down
10 changes: 10 additions & 0 deletions src/entities/merch/api/merch-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { MerchResponse } from '../types';
import { ApiServices } from '@/shared/types';

export class MerchApi {
constructor(private readonly services: ApiServices) {}

public queryMerchCatalog() {
return this.services.rest.get<MerchResponse>(`merch/filelist.json`);
}
}
36 changes: 36 additions & 0 deletions src/entities/merch/helpers/transform-merch-catalog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { v4 as uuidv4 } from 'uuid';

import { ApiMerchItem, ApiMerchItemAdapt, MerchProduct, MerchResponse } from '../types';

export const transformMerchCatalog = (data: MerchResponse): MerchProduct[] => {
const products: MerchProduct[] = [];
const baseUrl = process.env.API_BASE_URL;
const processCategory = (category: ApiMerchItemAdapt, parentTags: string[]) => {
for (const [key, value] of Object.entries(category)) {
if (isApiMerchItem(value)) {
products.push({
id: uuidv4(),
name: key,
title: value.name,
preview: value.preview.map((path) => `${baseUrl}/${path}`),
download: value.download.map((path) => `${baseUrl}/${path}`),
tags: parentTags,
});
} else {
processCategory(value, [...parentTags, key]);
}
}
};

for (const [categoryName, categoryData] of Object.entries(data)) {
processCategory(categoryData, [categoryName]);
}

return products;
};
Comment on lines +5 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Add error handling and improve URL construction.

The transformation function works well for flattening the nested catalog structure, but lacks error handling and proper URL construction. Consider these improvements:

  1. Add validation for baseUrl to ensure it's defined
  2. Implement URL joining logic to prevent double slashes if baseUrl ends with a slash or path starts with one
  3. Add try-catch blocks to handle errors during transformation


const isApiMerchItem = (item: unknown): item is ApiMerchItem => {
return Boolean(
item && typeof item === 'object' && 'name' in item && 'preview' in item && 'download' in item,
);
};
3 changes: 3 additions & 0 deletions src/entities/merch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type { MerchProduct } from './types';
export { MerchCard } from './ui/merch-card/merch-card';
export { merchStore } from './model/store';
18 changes: 18 additions & 0 deletions src/entities/merch/model/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { transformMerchCatalog } from '../helpers/transform-merch-catalog';
import { api } from '@/shared/api/api';

class MerchStore {
public loadMerchCatalog = async () => {
try {
const res = await api.merch.queryMerchCatalog();

if (res.isSuccess) {
return transformMerchCatalog(res.result);
}
} catch (e) {
console.error(e);
}
};
Comment on lines +5 to +15
Copy link
Contributor

@coderabbitai coderabbitai bot May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Improve error handling in loadMerchCatalog method.

Currently, the method returns undefined when an error occurs, which could lead to confusing UI states. Consider returning an empty array or error state that components can properly handle.

public loadMerchCatalog = async () => {
  try {
    const res = await api.merch.queryMerchCatalog();

    if (res.isSuccess) {
      return transformMerchCatalog(res.result);
    }
+   return []; // Return empty array if request was not successful
  } catch (e) {
    console.error(e);
+   return []; // Return empty array on error
  }
};
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public loadMerchCatalog = async () => {
try {
const res = await api.merch.queryMerchCatalog();
if (res.isSuccess) {
return transformMerchCatalog(res.result);
}
} catch (e) {
console.error(e);
}
};
public loadMerchCatalog = async () => {
try {
const res = await api.merch.queryMerchCatalog();
if (res.isSuccess) {
return transformMerchCatalog(res.result);
}
return []; // Return empty array if request was not successful
} catch (e) {
console.error(e);
return []; // Return empty array on error
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for improving merchStore class

gh issue create \
  --repo rolling-scopes/site \
  --title "Improve error handling and typing in merchStore class" \
  --body "## Description

Enhance the \`merchStore\` class in \`src/entities/merch/model/store.ts\` with better error handling and type safety.

## Tasks

- [ ] Update \`loadMerchCatalog\` method to return an empty array instead of \`undefined\` when errors occur or when the request is not successful
- [ ] Add proper TypeScript typing to the class and its methods
- [ ] Ensure consistent error handling patterns across the store

## Context

This issue was created as a follow-up to review comments on PR #889 that were valid but out of scope for that feature implementation.

## References

- PR: https://github.com/rolling-scopes/site/pull/889
- Discussion: https://github.com/rolling-scopes/site/pull/889#discussion_r2085061813
- Requested by: @ansivgit" \
  --assignee ansivgit

Length of output: 49


@ansivgit, I've created issue #975 to track the improvements for the merchStore class, including better error handling and proper typing. The issue has been assigned to you and includes references back to this PR and the review discussion.

}

export const merchStore = new MerchStore();
27 changes: 27 additions & 0 deletions src/entities/merch/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export type ApiMerchItem = {
name: string;
preview: string[];
download: string[];
};

type ApiMerchCategory = {
[key: string]: ApiMerchItem;
};

type ApiMerchData = {
[category: string]: ApiMerchCategory;
};

export type ApiMerchItemAdapt = ApiMerchItem | ApiMerchCategory | ApiMerchData;
export type MerchResponse = {
[category: string]: ApiMerchData;
};

export type MerchProduct = {
id: string;
name: string;
title: string;
preview: string[];
download: string[];
tags: string[];
};
178 changes: 178 additions & 0 deletions src/entities/merch/ui/merch-card/merch-card.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
.merch-card {
position: relative;

overflow: hidden;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Overflow hidden may clip pagination dots
.merch-card { overflow: hidden; } will clip any absolutely-positioned .swipe-dots placed outside its bounds (e.g. bottom: -15px). Consider adjusting overflow to visible on the lower section or repositioning dots within the container.

display: flex;
flex-direction: column;

width: 100%;
max-width: 320px;
height: 100%;
margin: 0 auto;
border-radius: 5px;

background-color: $color-white;
box-shadow: 0 4px 12px 0 hsla(from $color-black h s l / $opacity-10);

transition: box-shadow 0.3s ease;

&:hover {
box-shadow:
0 1px 5px hsla(from $color-black h s l / $opacity-40),
0 2px 8px -8px hsla(from $color-black h s l / $opacity-40);
}
}

.preview-wrap {
position: relative;
display: flex;
height: 180px;

&::after {
pointer-events: none;
content: '';

position: absolute;
top: 0;
left: 0;

width: 100%;
height: 100%;

background: linear-gradient(
to bottom,
transparent 0%,
hsla(from $color-black h s l / $opacity-10) 100%
);
}
Comment on lines +31 to +47
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

πŸ› οΈ Refactor suggestion

Add explicit z-index to gradient overlay
The &::after pseudo-element in .preview-wrap creates a gradient overlay but has no z-index. Without it, stacking of images, buttons, or dots may be inconsistent. Add z-index: 1; to the pseudo-element and ensure interactive elements (e.g. swipe buttons) sit above it (z-index: 2;).

}

.info-wrap {
display: flex;
flex: 1 1;
gap: 10px;
align-items: center;
justify-content: space-between;

padding: 16px;
}

.preview {
width: 100%;
height: 100%;
object-fit: contain;
}

.download {
position: absolute;
right: 10px;
bottom: 10px;

padding: 10px;
border-radius: 100%;

opacity: 0.8;
background-color: $color-yellow;
box-shadow: 0 4px 12px 0 hsla(from $color-black h s l / $opacity-10);

&:hover {
opacity: 1;
}
}

.download-img {
width: 20px;
height: 20px;
border-radius: 5px;
background-color: $color-yellow;
}

.swipe-btn {
cursor: pointer;

position: absolute;
z-index: 2;
top: 50%;
transform: translateY(-50%);

display: flex;
align-items: center;
justify-content: center;

width: 36px;
height: 36px;
border: none;
border-radius: 50%;

font-size: 20px;
color: $color-white;

background-color: rgb(0 0 0 / 30%);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use this syntax:
background-color: hsla(from $color-black h s l / $opacity-30);


transition:
background-color 0.2s ease,
transform 0.2s ease;

.arrow-img {
filter: brightness(0) invert(1);
}

&.is-prev {
left: 8px;
}

&.is-next {
right: 8px;
}
Comment on lines +120 to +126
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make this

Suggested change
&.is-prev {
left: 8px;
}
&.is-next {
right: 8px;
}
&.is-prev,
&.is-next {
left: 8px;
}


&:hover {
background-color: rgb(0 0 0 / 60%);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same, use hsla

}

&:disabled {
cursor: default;
opacity: 0.3;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use $opacity-30

}
}

.swipe-dots {
position: absolute;
z-index: 2;
bottom: -15px;
left: 50%;
transform: translateX(-50%);

display: flex;
gap: 6px;

.swipe-dot {
width: 8px;
height: 8px;
border-radius: 50%;

background-color: rgb(0 0 0 / 20%);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use hsla


transition: background-color all 0.3s ease;

Comment on lines +153 to +156
Copy link
Contributor

@coderabbitai coderabbitai bot May 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix transition property syntax
transition: background-color all 0.3s ease; is invalid. It should be either:

transition: background-color 0.3s ease;

or

transition: all 0.3s ease;

Please correct this to ensure the active dot transitions smoothly.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good point, please fix it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No description provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A summary of the edits CodeRabbit can apply:

Fix the transition property syntax by removing the invalid "all" keyword in src/entities/merch/ui/merch-card/merch-card.module.scss

A plan of each step that CodeRabbit will take in order to execute the edits for each file (with inline rough draft snippets):

✏️ src/entities/merch/ui/merch-card/merch-card.module.scss

Open src/entities/merch/ui/merch-card/merch-card.module.scss and navigate to the .swipe-dot block. Locate the line:

    transition: background-color all 0.3s ease;

Replace it exactly with:

    transition: background-color 0.3s ease;

Save the file, restart your SCSS compiler or development server, and verify in the browser that the swipe dots’ background-color now transitions smoothly without any syntax errors.

A summary of the context that CodeRabbit is considering across the codebase:

We’ll use ripgrep to locate the invalid transition syntax and confirm its exact line number and surrounding context before editing.

  • βœ… Create PR with these edits

  • πŸ“Œ Create commit in current branch

  • πŸ“‹ Get copyable edits

&.active {
background-color: $color-yellow;
}
}
}

.swipeable-area {
touch-action: pan-y;

position: relative;

display: flex;
flex: 1;
align-items: center;
justify-content: center;
}

@media (max-width: $mobile-landscape-width) {
.swipe-btn {
display: none;
}
}
Loading