Compare commits
6 Commits
56f3f94e41
...
886cf4b5c4
| Author | SHA1 | Date | |
|---|---|---|---|
| 886cf4b5c4 | |||
| fc588f9e66 | |||
| f08ee51332 | |||
| 9ded41db3c | |||
| 0697e9ad72 | |||
| caff3fe7e3 |
+3
-2
@@ -4,8 +4,9 @@ import { Footer } from '$widgets/Footer';
|
|||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Portfolio',
|
title: 'Ilia Mashkov — Portfolio',
|
||||||
description: 'Portfolio',
|
description: 'Portfolio of Ilia Mashkov, a frontend software engineer.',
|
||||||
|
icons: { icon: '/favicon.svg' },
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000" fill="none" xmlns:v="https://vecta.io/nano"><path d="M1000 500c0 276.142-223.858 500-500 500S0 776.142 0 500 223.858 0 500 0s500 223.858 500 500z" fill="#f4f0e8"/><path d="M548.399 188.863l5.08 2.98 5.31 3.14 10.86 6.36 37.64 22.31 24.31 14.34 22.14 13.01 21.68 12.74 27.59 16.33 11.89 7.04 17.82 10.6 10.15 5.97 2.29 1.32 4.34 2.48c12.01 6.94 19.11 15.63 22.82 29.08 2.58 8.48 6.64 16.47 10.39 24.48l13.44 29.44 1.19 2.65 15.58 35.08 8.47 18.57 10.13 22.46 9.25 20.24 9.32 20.38.9 1.99 14.33 32.09 11.12 24.38c5.32 11.45 7.13 21.43 2.9 33.66-4.87 11.08-12.12 15.85-22.4 21.67l-13.85 8.17-8.82 5.23-1.77 1.04-11.03 6.52-25.76 15.31-33.84 20.14-16.66 9.86-3.34 1.98-18.26 10.84-39.33 23.24-1.83 1.07-3.6 2.11-46.12 27.36-23.39 13.9-17.5 10.44-28.56 16.88-20.14 11.89c-18.05 10.75-34.67 20.4-56.43 15.11-9.12-2.9-17.13-7.74-25.31-12.63l-2.94-1.75-15.97-9.6-15.4-9.02c-7.83-4.5-15.55-9.17-23.25-13.88-10.2-6.23-20.48-12.28-30.85-18.22l-28.56-16.9-17.22-10.26-2.7-1.59-5.38-3.17-42.96-25.56-28.22-16.69-23.55-13.96-16.62-9.9-1.74-1.03-25.73-15.14-33.78-20.03-4.23-2.56c-11.94-7.23-18.46-13.09-22.47-26.69-1.71-8.23-.74-15.3 2.59-22.88.15-.37.15-.37.94-2.22 3.26-7.6 6.71-15.11 10.2-22.61l5.64-12.2 1.12-2.43 8.46-18.9 14.81-33.18 1.24-2.74 17.94-39.37 16.42-36.34 9.38-20.84 5.25-11.62 4.32-9.55 4.95-10.98 1.49-3.29c2.47-5.5 4.58-10.9 6.14-16.74 4.02-12.73 15.77-19.9 26.68-26.16l5.74-3.31 9.92-5.71 22.65-13.35 27.07-16.07 47.41-28 10.09-6 2.03-1.2 32.84-19.35 19.12-11.26 8.05-4.74 10.1-6.08c9.76-5.92 18.87-10.15 29.98-12.93l1.88-.54c22.28-4.83 45.18 1.99 64.12 13.29zm-91.34 25.69c-3.68 2.68-7.6 4.77-11.61 6.91-5.64 3.05-11.15 6.34-16.67 9.59l-3.84 2.25-27.29 16.28-25.52 14.9-25.46 15.06-30.61 18.02-19.66 11.55-2.62 1.54-14.1 8.34-9.84 5.74-2.07 1.2-5.37 3.08c-4.44 3.11-7.64 6.09-8.62 11.6.77 4.33 2.45 6.6 5.62 9.5 3.56 2.44 7.29 4.53 11.06 6.63l6.56 3.73 3.29 1.87c4.3 2.46 8.54 5.01 12.78 7.58l25.12 14.88 21.69 12.81 21.25 12.56 54.94 32.57 33.61 19.8 9.7 5.69 6.17 3.62 7.6 4.58c12.1 7.39 25.07 9.88 39.23 7.12 13.28-4.14 25.32-12.74 37.09-19.87 4.27-2.59 8.56-5.12 12.91-7.57 6.26-3.53 12.41-7.23 18.56-10.94l27.82-16.5 32.29-19.03 17.95-10.59 20.13-12 26.09-15.48 5.51-3.24 17.27-10.01 3.09-1.78 5.68-3.23c7.71-4.47 7.71-4.47 9.61-9.2.65-4.46.51-6.29-2.14-9.98-2.67-3.03-5.58-4.96-9.09-6.97l-1.79-1.04-5.73-3.26-3.97-2.29-7.81-4.47-12.41-7.24-3.95-2.32-9-5.32-5.36-3.17-2.74-1.62-22.04-12.95-30.97-18.37-51.53-30.48-22.52-13.16-5.62-3.29-15.63-9.27-2.33-1.39-4.01-2.46c-23.38-14.06-50.24-5.72-70.7 9.49zm-214.66 153.56a627.1 627.1 0 0 0-13.62 28.56l-1.83 4.07-5.55 12.37-1.67 3.72-1.6 3.58-1.59 3.55-3.17 7.09-12.47 27.56-22.07 49.01-18.69 41.38-3.11 6.81-1.99 4.35-.97 2.12-5.46 11.84-2.21 4.8-.97 2.04c-1.76 3.88-2.75 6.91-2.03 11.15 3.81 6.22 9.69 9.08 15.94 12.56l6.5 3.71 3.32 1.88 14.68 8.6 5.44 3.22 2.61 1.55 11.45 6.73 17.4 10.36 16.35 9.67 13.06 7.78 15.18 9 1.9 1.11 9.48 5.54 20.72 12.47c4.07 2.49 8.2 4.89 12.35 7.26l16.93 9.93 18.25 10.88 2.86 1.68 8.58 5.07 40.42 23.94 22.83 13.5 20.32 12.11 17.75 10.5 22.28 13.48 2.87 1.76 2.57 1.58c1.96 1.13 1.96 1.13 2.96 1.13v-292l-12-3c-12.22-4.97-23.51-12.32-34.79-19.09l-16.24-9.55-27.3-16.16-32.83-19.39-1.85-1.08-1.86-1.1-3.75-2.19-1.9-1.11-24.79-14.73-22.5-13.29-25.46-15.07-17.72-10.49c-6.31-3.72-12.56-7.53-18.74-11.46-2.27-1.29-2.27-1.29-4.27-1.29zm512.06.85l-2.2 1.36-2.47 1.51-2.64 1.65-5.52 3.39-2.73 1.69-9.44 5.74-1.71 1.02-32.06 18.86-54.01 31.92-37.08 21.85-24.87 14.77-22.59 13.3-13.69 8.06c-10.31 6.2-19.16 10.39-31.05 13.03v292l18.81-10.56 2.15-1.28 4.62-2.73 7.53-4.44 32.91-19.6 25.98-15.39 26.56-15.75 22.13-13.06 28.08-16.64 41.77-24.75 38.46-22.8 26.08-15.5 19.73-11.69 22.44-13.25 5.82-3.43 4.02-2.35 6.22-3.65 1.87-1.09c4.04-2.39 7.68-4.76 9.82-9.04.43-5.52-.56-9.03-2.96-13.98l-.94-1.96-3.04-6.25-2.09-4.36-4.12-8.59c-2.26-4.69-4.42-9.42-6.54-14.17l-1.04-2.34-5.92-13.33-2.91-6.58-1.52-3.42-13.69-30.18-15.17-33.71-18.88-41.69-10.04-22.18-.91-2.08-4.21-9.66-1.48-3.38-1.28-2.95c-1.64-2.85-3.07-3.71-6.2-2.34z" fill="#1334da"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 391 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 128 B |
@@ -1 +0,0 @@
|
|||||||
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 385 B |
@@ -0,0 +1,40 @@
|
|||||||
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { PBHttpError } from '../error';
|
||||||
|
import { getCollection } from './client';
|
||||||
|
|
||||||
|
describe('getCollection', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when PocketBase is unreachable', () => {
|
||||||
|
it('returns an empty list instead of throwing', async () => {
|
||||||
|
vi.stubEnv('PB_URL', 'http://localhost:8090');
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new TypeError('fetch failed')));
|
||||||
|
|
||||||
|
const result = await getCollection('projects');
|
||||||
|
|
||||||
|
expect(result.items).toEqual([]);
|
||||||
|
expect(result.totalItems).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when PocketBase returns an HTTP error', () => {
|
||||||
|
it('rethrows PBHttpError', async () => {
|
||||||
|
vi.stubEnv('PB_URL', 'http://localhost:8090');
|
||||||
|
vi.stubGlobal(
|
||||||
|
'fetch',
|
||||||
|
vi.fn().mockResolvedValue({
|
||||||
|
ok: false,
|
||||||
|
status: 403,
|
||||||
|
statusText: 'Forbidden',
|
||||||
|
json: vi.fn(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(getCollection('projects')).rejects.toBeInstanceOf(PBHttpError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import { PBHttpError } from './error';
|
import { PBHttpError } from '../error';
|
||||||
import type { ListResponse } from './types';
|
import type { ListResponse } from '../types';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Native fetch wrapper for PocketBase API requests.
|
* Native fetch wrapper for PocketBase API requests.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Required in production; falls back to localhost in development. */
|
|
||||||
const PB_URL = process.env.PB_URL ?? (process.env.NODE_ENV === 'development' ? 'http://127.0.0.1:8090' : undefined);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for PocketBase collection fetching.
|
* Options for PocketBase collection fetching.
|
||||||
*/
|
*/
|
||||||
@@ -40,12 +37,15 @@ export type PBFetchOptions = {
|
|||||||
* Fetch a list of records from a PocketBase collection.
|
* Fetch a list of records from a PocketBase collection.
|
||||||
*/
|
*/
|
||||||
export async function getCollection<T>(collection: string, options: PBFetchOptions = {}): Promise<ListResponse<T>> {
|
export async function getCollection<T>(collection: string, options: PBFetchOptions = {}): Promise<ListResponse<T>> {
|
||||||
const { sort, filter, expand, tags, revalidate } = options;
|
/* Required in production; falls back to localhost in development. */
|
||||||
|
const pbUrl = process.env.PB_URL ?? (process.env.NODE_ENV === 'development' ? 'http://127.0.0.1:8090' : undefined);
|
||||||
|
|
||||||
if (!PB_URL) {
|
if (!pbUrl) {
|
||||||
throw new Error('PB_URL is required in production');
|
throw new Error('PB_URL is required in production');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { sort, filter, expand, tags, revalidate } = options;
|
||||||
|
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
if (sort) {
|
if (sort) {
|
||||||
params.set('sort', sort);
|
params.set('sort', sort);
|
||||||
@@ -57,8 +57,9 @@ export async function getCollection<T>(collection: string, options: PBFetchOptio
|
|||||||
params.set('expand', expand);
|
params.set('expand', expand);
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${PB_URL}/api/collections/${collection}/records?${params.toString()}`;
|
const url = `${pbUrl}/api/collections/${collection}/records?${params.toString()}`;
|
||||||
|
|
||||||
|
try {
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
next: {
|
next: {
|
||||||
tags: tags ?? [],
|
tags: tags ?? [],
|
||||||
@@ -71,6 +72,13 @@ export async function getCollection<T>(collection: string, options: PBFetchOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res.json();
|
return res.json();
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof PBHttpError) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
console.warn(`[getCollection] "${collection}" unreachable — returning empty list`, err);
|
||||||
|
return { items: [], page: 1, perPage: 0, totalItems: 0, totalPages: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './client';
|
export * from './client/client';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
/* === GRID === */
|
/* === GRID === */
|
||||||
--grid-gap: var(--space-3);
|
--grid-gap: var(--space-3);
|
||||||
--section-content-width: 72rem;
|
--section-content-width: 72rem;
|
||||||
|
|
||||||
/* === ANIMATION === */
|
/* === ANIMATION === */
|
||||||
--ease-default: ease;
|
--ease-default: ease;
|
||||||
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
@@ -93,6 +94,7 @@
|
|||||||
--duration-normal: 150ms;
|
--duration-normal: 150ms;
|
||||||
--duration-slow: 350ms;
|
--duration-slow: 350ms;
|
||||||
--duration-spring: 220ms;
|
--duration-spring: 220ms;
|
||||||
|
--delay-normal: 200ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
@@ -371,3 +373,41 @@
|
|||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Section body slide-in from right */
|
||||||
|
::view-transition-old(section-body) {
|
||||||
|
animation-name: section-body-out;
|
||||||
|
animation-duration: var(--duration-normal);
|
||||||
|
animation-timing-function: var(--ease-default);
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
::view-transition-new(section-body) {
|
||||||
|
animation-name: section-body-in;
|
||||||
|
animation-duration: var(--duration-spring);
|
||||||
|
animation-timing-function: var(--ease-spring);
|
||||||
|
animation-fill-mode: both;
|
||||||
|
animation-delay: var(--delay-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes section-body-out {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-12px) scale(0.98);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes section-body-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(48px) scale(0.98);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export async function Footer() {
|
|||||||
const socials = contacts?.expand?.socials ?? [];
|
const socials = contacts?.expand?.socials ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="fixed bottom-0 left-0 right-0 z-50 h-footer md:h-footer-wide brutal-border-top bg-background px-8 lg:px-16 flex items-center">
|
<footer className="fixed bottom-0 left-0 right-0 z-50 h-footer md:h-footer-wide brutal-border-top bg-background px-4 sm:px-8 lg:px-16 flex items-center">
|
||||||
<div className="w-full flex flex-row justify-between gap-4">
|
<div className="w-full flex flex-row justify-between gap-4">
|
||||||
<div className="flex flex-wrap items-center gap-6 sm:gap-4">
|
<div className="flex flex-wrap items-center gap-6 sm:gap-4">
|
||||||
{email && (
|
{email && (
|
||||||
|
|||||||
Reference in New Issue
Block a user