Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ef0c78c
feat(layout): add SidebarLayout and reorganize sidebar module
kapitulin24 May 12, 2026
7482dec
feat(store): add createStore factory and team slug store
kapitulin24 May 12, 2026
25f4ac5
refactor(entities): mark type namespaces as type-only exports
kapitulin24 May 12, 2026
b791c70
style(theme): migrate global palette to OKLCH and align sidebar tokens
kapitulin24 May 12, 2026
4ead440
chore(infra): add Imagor env vars to dev .env.example
kapitulin24 May 12, 2026
7f250d9
feat(entities): structured team avatars, invitation URLs without slug…
kapitulin24 May 14, 2026
1791083
feat(shared): add profile teams route and export Empty from shared ui
kapitulin24 May 14, 2026
7f420f8
refactor(asset): rename file entity
kapitulin24 May 14, 2026
d99323d
feat(pages/profile): teams tab, split teams/invites UI, profile model…
kapitulin24 May 14, 2026
6ba57d3
refactor(pages/auth): group auth flows into FSD-style slices
kapitulin24 May 14, 2026
435b534
feat(team/settings): migrate to real API with react-hook-form and loa…
kapitulin24 May 16, 2026
5a6d9eb
feat(team/members): replace mock data with real API and add member co…
kapitulin24 May 16, 2026
e896984
feat(widgets): replace team switcher with teams dropdown, extract tab…
kapitulin24 May 17, 2026
342899a
refactor(api): rename invite → invitation throughout codebase
kapitulin24 May 17, 2026
09e4aa5
feat(team-entity): add roles/statuses config, SlugField, update schem…
kapitulin24 May 17, 2026
5421771
feat(user-entity): add UserAvatar, update user schemas and API
kapitulin24 May 17, 2026
c88ed04
feat(teams): add create, invite, remove team features and active-team…
kapitulin24 May 17, 2026
a9c62f0
feat(team-page): add members management — roles, statuses, remove dialog
kapitulin24 May 17, 2026
ca4da33
feat(team-page): add invitations and team API hooks, refactor settings
kapitulin24 May 17, 2026
a96ee5e
refactor(profile-page): update me-page and teams-page
kapitulin24 May 17, 2026
94d65a6
refactor(shared): flatten sidebar structure, update shared UI
kapitulin24 May 17, 2026
844ceff
chore: misc type
kapitulin24 May 17, 2026
7080692
chore: format
kapitulin24 May 17, 2026
4531919
Merge branch 'dev' into feat/ui-bll-integration
kapitulin24 May 17, 2026
2a27f15
chore(docker): upgrade Node base image from 20 to 23
kapitulin24 May 18, 2026
3a9b340
Merge remote-tracking branch 'origin/feat/ui-bll-integration' into fe…
kapitulin24 May 18, 2026
4fb4af7
fix(docker): pin pnpm version and set CI env in prod build
kapitulin24 May 18, 2026
e2b6dee
feat(team): async slug validation via zod resolver
kapitulin24 May 19, 2026
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @kapitulin24 @perekljuchatel
* @kapitulin24
7 changes: 4 additions & 3 deletions Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
FROM node:20-alpine AS base
FROM node:23-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
RUN corepack enable && corepack prepare pnpm@10.30.1 --activate
WORKDIR /app

FROM base AS deps
ENV CI=true
RUN apk add --no-cache libc6-compat

COPY pnpm-lock.yaml ./
Expand Down Expand Up @@ -35,7 +36,7 @@ COPY . .

RUN --mount=type=cache,target=/app/.next/cache pnpm run build

FROM node:20-alpine AS runner
FROM node:23-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production \
Expand Down
2 changes: 1 addition & 1 deletion app/(auth)/forgot-password/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ForgotPasswordPage as default } from 'pages/forgot-password';
export { ForgotPasswordPage as default } from 'pages/auth/forgot-password';
2 changes: 1 addition & 1 deletion app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { SigninPage as default } from 'pages/signin';
export { SigninPage as default } from 'pages/auth/signin';
2 changes: 1 addition & 1 deletion app/(auth)/signup/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { SignupPage as default } from 'pages/signup';
export { SignupPage as default } from 'pages/auth/signup';
29 changes: 10 additions & 19 deletions app/(protected)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
import 'app/styles/global.css';
import { Separator, SidebarInset, SidebarProvider, SidebarTrigger } from 'shared/ui';
import { AppSidebar } from 'app/layouts/BaseLayout';
import { SidebarLayout } from 'app/layouts/SidebarLayout';
import { cookies } from 'next/headers';
import type { ReactNode } from 'react';
import { SIDEBAR_COOKIE_NAME } from 'shared/ui';

export default async function RootLayout({ children }: { children: React.ReactNode }) {
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 self-center! data-[orientation=vertical]:h-6"
/>
</header>
<div className="p-4">{children}</div>
</SidebarInset>
</SidebarProvider>
);
export default async function Layout({ children }: { children: ReactNode }) {
const cookieStore = await cookies();
const stored = cookieStore.get(SIDEBAR_COOKIE_NAME)?.value === 'true';
const defaultSidebarOpen = stored ?? true;

return <SidebarLayout defaultOpen={defaultSidebarOpen}>{children}</SidebarLayout>;
}
1 change: 0 additions & 1 deletion app/(protected)/page.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions app/(protected)/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { PageLayout } from 'app/layouts/PageLayout';
import { TabsNav } from 'pages/profile';
import { profileTabs } from 'pages/profile';

export default function ProfileLayout({ children }: { children: React.ReactNode }) {
return (
<PageLayout
title="Профиль"
description="Управляйте данными аккаунта, безопасностью и уведомлениями."
nav={<TabsNav />}
tabs={profileTabs}
>
{children}
</PageLayout>
Expand Down
1 change: 1 addition & 0 deletions app/(protected)/profile/teams/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TeamsPage as default } from 'pages/profile';
1 change: 1 addition & 0 deletions app/(protected)/team/invitations/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { InvitationsPage as default } from 'pages/team';
1 change: 0 additions & 1 deletion app/(protected)/team/invites/page.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions app/(protected)/team/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { TabsNav } from 'pages/team';
import { PageLayout } from 'app/layouts/PageLayout';
import { Badge } from 'shared/ui';
import { teamTabs } from 'pages/team';

export default function TeamLayout({ children }: { children: React.ReactNode }) {
return (
<PageLayout
title="Управление командой"
description="Управляйте участниками рабочего пространства, ожидающими приглашениями, ролями и правами доступа."
badge={<Badge variant="secondary">8 участников</Badge>}
nav={<TabsNav />}
tabs={teamTabs}
>
{children}
</PageLayout>
Expand Down
5 changes: 4 additions & 1 deletion infra/dev/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,7 @@ S3_BUCKET_NAME=''
S3_ENDPOINT=''
S3_REGION=''
S3_ACCESS_KEY=''
S3_SECRET_KEY=''
S3_SECRET_KEY=''

IMAGOR_URL=http://localhost
IMAGOR_SECRET=super-secret
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"axios": "^1.15.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"immer": "^11.1.7",
"input-otp": "^1.4.2",
"lucide-react": "^0.574.0",
"next": "^16.1.6",
Expand Down
14 changes: 11 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src/app/layouts/BaseLayout.tsx

This file was deleted.

16 changes: 15 additions & 1 deletion src/app/layouts/PageLayout.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
export { PageLayout } from 'widgets/page-layout';
import { ComponentProps } from 'react';
import { PageLayout as PageLayoutParent } from 'widgets/page-layout';
import { TabsNav } from 'widgets/tabs-nav';

interface PageLayoutProps extends Omit<ComponentProps<typeof PageLayoutParent>, 'nav'> {
tabs: ComponentProps<typeof TabsNav>['tabs'];
}

export function PageLayout({ children, tabs, ...props }: PageLayoutProps) {
return (
<PageLayoutParent nav={tabs && tabs.length ? <TabsNav tabs={tabs} /> : undefined} {...props}>
{children}
</PageLayoutParent>
);
}
23 changes: 23 additions & 0 deletions src/app/layouts/SidebarLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentProps } from 'react';
import { TeamSlugSync } from 'features/teams/active-team';
import { Separator, SidebarInset, SidebarProvider, SidebarTrigger } from 'shared/ui';
import { AppSidebar } from 'widgets/app-sidebar';

export function SidebarLayout({ children, ...props }: ComponentProps<typeof SidebarProvider>) {
return (
<SidebarProvider {...props}>
<TeamSlugSync />
<AppSidebar />
<SidebarInset>
<header className="flex h-14 shrink-0 items-center gap-2 border-b px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 self-center! data-[orientation=vertical]:h-6"
/>
</header>
<div className="p-4">{children}</div>
</SidebarInset>
</SidebarProvider>
);
}
131 changes: 68 additions & 63 deletions src/app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,74 +47,79 @@
}

:root {
--radius: 0.5rem;
--background: hsl(0 0% 100%);
--foreground: hsl(222 47% 11%);
--card: hsl(0 0% 100%);
--card-foreground: hsl(222 47% 11%);
--popover: hsl(0 0% 100%);
--popover-foreground: hsl(222 47% 11%);
--primary: hsl(207 100% 52%);
--primary-foreground: hsl(0 0% 100%);
--secondary: hsl(210 40% 96.1%);
--secondary-foreground: hsl(222.2 47.4% 11.2%);
--muted: hsl(210 40% 96.1%);
--muted-foreground: hsl(215.4 16.3% 46.9%);
--accent: hsl(210 40% 96.1%);
--accent-foreground: hsl(222.2 47.4% 11.2%);
--destructive: hsl(0 84.2% 60.2%);
--border: hsl(214.3 31.8% 91.4%);
--input: hsl(214.3 31.8% 91.4%);
--ring: hsl(207 100% 52%);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: hsl(0 0% 98%);
--sidebar-foreground: hsl(240 5.3% 26.1%);
--sidebar-primary: hsl(240 5.9% 10%);
--sidebar-primary-foreground: hsl(0 0% 98%);
--sidebar-accent: hsl(240 4.8% 95.9%);
--sidebar-accent-foreground: hsl(240 5.9% 10%);
--sidebar-border: hsl(220 13% 91%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--background: oklch(1 0 0);
--foreground: oklch(0.3211 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.3211 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.3211 0 0);
--primary: oklch(0.6231 0.188 259.8145);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.967 0.0029 264.5419);
--secondary-foreground: oklch(0.4461 0.0263 256.8018);
--muted: oklch(0.9846 0.0017 247.8389);
--muted-foreground: oklch(0.551 0.0234 264.3637);
--accent: oklch(0.9514 0.025 236.8242);
--accent-foreground: oklch(0.3791 0.1378 265.5222);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.9276 0.0058 264.5313);
--input: oklch(0.9276 0.0058 264.5313);
--ring: oklch(0.6231 0.188 259.8145);
--chart-1: oklch(0.6231 0.188 259.8145);
--chart-2: oklch(0.5461 0.2152 262.8809);
--chart-3: oklch(0.4882 0.2172 264.3763);
--chart-4: oklch(0.4244 0.1809 265.6377);
--chart-5: oklch(0.3791 0.1378 265.5222);
--sidebar: oklch(0.9846 0.0017 247.8389);
--sidebar-foreground: oklch(0.3211 0 0);
--sidebar-primary: oklch(0.6231 0.188 259.8145);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.9514 0.025 236.8242);
--sidebar-accent-foreground: oklch(0.3791 0.1378 265.5222);
--sidebar-border: oklch(0.9276 0.0058 264.5313);
--sidebar-ring: oklch(0.6231 0.188 259.8145);
--font-sans: Inter, sans-serif;
--font-serif: Source Serif 4, serif;
--font-mono: JetBrains Mono, monospace;
--radius: 0.375rem;
--link: oklch(1 0 89.876 / 0);
--link-foreground: oklch(54.65% 0.246 262.87);
}

.dark {
--background: hsl(222.2 84% 4.9%);
--foreground: hsl(210 40% 98%);
--card: hsl(222.2 84% 4.9%);
--card-foreground: hsl(210 40% 98%);
--popover: hsl(222.2 84% 4.9%);
--popover-foreground: hsl(210 40% 98%);
--primary: hsl(210 40% 98%);
--primary-foreground: hsl(222.2 47.4% 11.2%);
--secondary: hsl(217.2 32.6% 17.5%);
--secondary-foreground: hsl(210 40% 98%);
--muted: hsl(217.2 32.6% 17.5%);
--muted-foreground: hsl(215 20.2% 65.1%);
--accent: hsl(217.2 32.6% 17.5%);
--accent-foreground: hsl(210 40% 98%);
--destructive: hsl(0 62.8% 30.6%);
--border: hsl(217.2 32.6% 17.5%);
--input: hsl(217.2 32.6% 17.5%);
--ring: hsl(212.7 26.8% 83.9%);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: hsl(240 5.9% 10%);
--sidebar-foreground: hsl(240 4.8% 95.9%);
--sidebar-primary: hsl(224.3 76.3% 48%);
--sidebar-primary-foreground: hsl(0 0% 100%);
--sidebar-accent: hsl(240 3.7% 15.9%);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%);
--sidebar-border: hsl(240 3.7% 15.9%);
--sidebar-ring: hsl(217.2 91.2% 59.8%);
--background: oklch(0.2046 0 0);
--foreground: oklch(0.9219 0 0);
--card: oklch(0.2686 0 0);
--card-foreground: oklch(0.9219 0 0);
--popover: oklch(0.2686 0 0);
--popover-foreground: oklch(0.9219 0 0);
--primary: oklch(0.6231 0.188 259.8145);
--primary-foreground: oklch(1 0 0);
--secondary: oklch(0.2686 0 0);
--secondary-foreground: oklch(0.9219 0 0);
--muted: oklch(0.2393 0 0);
--muted-foreground: oklch(0.7155 0 0);
--accent: oklch(0.3791 0.1378 265.5222);
--accent-foreground: oklch(0.8823 0.0571 254.1284);
--destructive: oklch(0.6368 0.2078 25.3313);
--destructive-foreground: oklch(1 0 0);
--border: oklch(0.3715 0 0);
--input: oklch(0.3715 0 0);
--ring: oklch(0.6231 0.188 259.8145);
--chart-1: oklch(0.7137 0.1434 254.624);
--chart-2: oklch(0.6231 0.188 259.8145);
--chart-3: oklch(0.5461 0.2152 262.8809);
--chart-4: oklch(0.4882 0.2172 264.3763);
--chart-5: oklch(0.4244 0.1809 265.6377);
--sidebar: oklch(0.2046 0 0);
--sidebar-foreground: oklch(0.9219 0 0);
--sidebar-primary: oklch(0.6231 0.188 259.8145);
--sidebar-primary-foreground: oklch(1 0 0);
--sidebar-accent: oklch(0.3791 0.1378 265.5222);
--sidebar-accent-foreground: oklch(0.8823 0.0571 254.1284);
--sidebar-border: oklch(0.3715 0 0);
--sidebar-ring: oklch(0.6231 0.188 259.8145);
--link: oklch(1 0 89.876 / 0);
--link-foreground: oklch(54.65% 0.246 262.87);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { api } from 'shared/api';
import { UploadFileData, UploadResponse } from '../model/types';
import { UploadResponse as UploadResponseSchema } from '../model/schemas';
import { UploadAssetData, UploadAssetResponse } from '../model/types';
import { UploadAssetResponse as UploadResponseSchema } from '../model/schemas';

export class UploadHttp {
static uploadFile(data: UploadFileData): Promise<UploadResponse> {
export class AssetHttp {
static uploadFile(data: UploadAssetData): Promise<UploadAssetResponse> {
const formData = new FormData();

formData.append('file', data.file);
// INCLUDED: SEE AT SWAGGER DOCS TO CONTEXT AND PROPS TOO
formData.append('context', data.context);

return api<UploadResponse>({
return api<UploadAssetResponse>({
url: '/upload',
method: 'POST',
data: formData,
Expand Down
3 changes: 3 additions & 0 deletions src/entities/asset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * as SAsset from './model/schemas';
export type * as TAsset from './model/types';
export { AssetHttp } from './api/http';
3 changes: 3 additions & 0 deletions src/entities/asset/model/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { GlobalSuccess } from 'shared/api';

export const UploadAssetResponse = GlobalSuccess;
Loading
Loading