Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e4cff96
feat(team): add team entity API client, queries, and validation schemas
kapitulin24 May 5, 2026
52f65b3
feat(user): update user entity API client, queries, and validation sc…
kapitulin24 May 5, 2026
e45d33d
feat(team): team area, PageLayout, shared UI; drop projects/tasks pages
kapitulin24 May 6, 2026
d97b8a0
style(page-layout): drop max-w-7xl
kapitulin24 May 6, 2026
6a054f6
refactor: decompose profile and auth logic into features and entities
soorq May 6, 2026
b0b2677
Merge remote-tracking branch 'origin/feat/teams-projects' into feat/t…
kapitulin24 May 6, 2026
f9760a5
refactor(ui): rename animation
kapitulin24 May 7, 2026
f67488e
fix(team): debounce member search by input value
kapitulin24 May 7, 2026
f23f448
feat(shared/ui): add CardSection, Select, and improve option/item var…
kapitulin24 May 7, 2026
99747b9
refactor(entities): add file upload entity and drop team/user avatar …
kapitulin24 May 7, 2026
9a98802
feat(upload-avatar): add UploadAvatar UI and useUploadAvatar mutation
kapitulin24 May 7, 2026
984437c
refactor(profile): use UploadAvatar feature and remove useUpdateAvatar
kapitulin24 May 7, 2026
9f6fe83
refactor(team): reorganize members/invites/roles/settings and add rol…
kapitulin24 May 7, 2026
b2909fe
chore(team): align app routes with renamed page exports
kapitulin24 May 7, 2026
b4ff9ce
feat(ui): add pending state to FloatingSaveBar
kapitulin24 May 7, 2026
e8fc7fb
feat(routing): nested profile routes and sidebar sub-navigation
kapitulin24 May 7, 2026
74fb9f8
feat(profile): split settings across me, security, and notifications …
kapitulin24 May 7, 2026
f8a93c2
Merge branch 'dev' into feat/teams-projects
kapitulin24 May 7, 2026
42e18bc
fix(format): prettier errors
kapitulin24 May 7, 2026
5cabc26
Merge remote-tracking branch 'origin/feat/teams-projects' into feat/t…
kapitulin24 May 7, 2026
8c260b1
fix build
kapitulin24 May 7, 2026
9a6d66f
clr default
kapitulin24 May 7, 2026
4b02fc3
refactor(app): re-export profile and team routes from pages modules
kapitulin24 May 7, 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: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ jobs:
NEXT_PUBLIC_FARO_URL=${{ vars.NEXT_PUBLIC_FARO_URL }}
NEXT_PUBLIC_FARO_APP_VERSION=${{ vars.NEXT_PUBLIC_FARO_APP_VERSION }}
NEXT_PUBLIC_APP_ENV=${{ vars.NEXT_PUBLIC_APP_ENV }}
NEXT_PUBLIC_FARO_APP_NAME=${{ vars.NEXT_PUBLIC_FARO_APP_NAME }}
NEXT_PUBLIC_FARO_APP_NAMESPACE=${{ vars.NEXT_PUBLIC_FARO_APP_NAMESPACE }}
SKIP_ENV_VALIDATION=true
push: true
tags: ${{ steps.meta.outputs.tags }}
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,11 @@ jobs:
env:
SKIP_ENV_VALIDATION: true
NODE_ENV: production
NEXT_PUBLIC_API_BASE_URL: ${{ vars.NEXT_PUBLIC_API_BASE_URL }}
NEXT_PUBLIC_FARO_URL: ${{ vars.NEXT_PUBLIC_FARO_URL }}
NEXT_PUBLIC_FARO_APP_VERSION: ${{ vars.NEXT_PUBLIC_FARO_APP_VERSION }}
NEXT_PUBLIC_APP_ENV: ${{ vars.NEXT_PUBLIC_APP_ENV }}
NEXT_PUBLIC_FARO_APP_NAME: ${{ vars.NEXT_PUBLIC_FARO_APP_NAME }}
NEXT_PUBLIC_FARO_APP_NAMESPACE: ${{ vars.NEXT_PUBLIC_FARO_APP_NAMESPACE }}
# - name: Build Storybook
# run: pnpm build-storybook
2 changes: 1 addition & 1 deletion app/(protected)/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ProfilePage as default } from 'pages/profile';
export { MePage as default } from 'pages/profile';
14 changes: 14 additions & 0 deletions app/(protected)/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { PageLayout } from 'app/layouts/PageLayout';
import { TabsNav } from 'pages/profile';

export default function ProfileLayout({ children }: { children: React.ReactNode }) {
return (
<PageLayout
title="Профиль"
description="Управляйте данными аккаунта, безопасностью и уведомлениями."
nav={<TabsNav />}
>
{children}
</PageLayout>
);
}
1 change: 1 addition & 0 deletions app/(protected)/profile/me/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MePage as default } from 'pages/profile';
1 change: 1 addition & 0 deletions app/(protected)/profile/notifications/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { NotificationsPage as default } from 'pages/profile';
7 changes: 6 additions & 1 deletion app/(protected)/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
export { ProfilePage as default } from 'pages/profile';
import { redirect } from 'next/navigation';
import { routes } from 'shared/config';

export default function ProfilePage() {
redirect(routes.profile.me());
}
1 change: 1 addition & 0 deletions app/(protected)/profile/security/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SecurityPage as default } from 'pages/profile';
1 change: 0 additions & 1 deletion app/(protected)/projects/page.tsx

This file was deleted.

1 change: 0 additions & 1 deletion app/(protected)/tasks/page.tsx

This file was deleted.

1 change: 1 addition & 0 deletions app/(protected)/team/invites/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { InvitesPage as default } from 'pages/team';
16 changes: 16 additions & 0 deletions app/(protected)/team/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TabsNav } from 'pages/team';
import { PageLayout } from 'app/layouts/PageLayout';
import { Badge } from 'shared/ui';

export default function TeamLayout({ children }: { children: React.ReactNode }) {
return (
<PageLayout
title="Управление командой"
description="Управляйте участниками рабочего пространства, ожидающими приглашениями, ролями и правами доступа."
badge={<Badge variant="secondary">8 участников</Badge>}
nav={<TabsNav />}
>
{children}
</PageLayout>
);
}
1 change: 1 addition & 0 deletions app/(protected)/team/members/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { MembersPage as default } from 'pages/team';
6 changes: 6 additions & 0 deletions app/(protected)/team/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { redirect } from 'next/navigation';
import { routes } from 'shared/config';

export default function TeamPage() {
redirect(routes.team.members());
}
1 change: 1 addition & 0 deletions app/(protected)/team/roles/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RolesPage as default } from 'pages/team';
1 change: 1 addition & 0 deletions app/(protected)/team/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Settings as default } from 'pages/team';
4 changes: 3 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,16 @@ const eslintConfig = defineConfig([
{
'**/{page,layout,loading,error,not-found,template,default,route}.{jsx,tsx}':
'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/!({page,layout,loading,error,not-found,template,default,route}).{jsx,tsx}':
'**/!({page,layout,loading,error,not-found,template,default,route,index,*.stories}).{jsx,tsx}':
'PASCAL_CASE',
'**/*{Error,Type,Types,Interface,Props,Dto,Response,Request,Contract,Contracts}.ts':
'PASCAL_CASE',
'**/!(*{Error,Type,Types,Interface,Props,Dto,Response,Request,Contract,Contracts,use}*).ts':
'KEBAB_CASE',
'**/*.{js,mjs,cjs,mts,cts}': 'KEBAB_CASE',
'**/use*.{ts,tsx}': 'CAMEL_CASE',
'**/*.stories.{jsx,tsx}': 'KEBAB_CASE',
'**/index.tsx': 'KEBAB_CASE',
},
{
ignoreMiddleExtensions: true,
Expand Down
4 changes: 2 additions & 2 deletions proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { trace } from '@opentelemetry/api';

const REFRESH_COOKIE = 'refresh';

const PROTECTED_PREFIXES = [routes.profile(), routes.projects(), routes.tasks()];
const PROTECTED_PREFIXES = [routes.profile.root(), routes.team.root()];
const PUBLIC_ONLY_ROUTES = [routes.auth.signin(), routes.auth.signup()];

function startsWithOneOf(pathname: string, prefixes: string[]) {
Expand All @@ -26,7 +26,7 @@ export function proxy(req: NextRequest) {
}

if (isPublicOnly && hasRefreshCookie) {
return NextResponse.redirect(new URL(routes.profile(), req.url));
return NextResponse.redirect(new URL(routes.profile.root(), req.url));
}

const response = NextResponse.next();
Expand Down
1 change: 1 addition & 0 deletions src/app/layouts/PageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { PageLayout } from 'widgets/page-layout';
12 changes: 12 additions & 0 deletions src/app/styles/animations.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
--animate-fade-destructive-input: fadeDestructiveInput 0.7s ease-in-out;
--animate-fade-in: fadeIn 0.7s ease-in-out forwards;
--animate-fade-out: fadeOut 0.7s ease-in-out forwards;
--animate-slide-in-up: slideInUp 0.2s ease-in-out forwards;

@keyframes headShake {
0% {
Expand Down Expand Up @@ -53,4 +54,15 @@
opacity: 0;
}
}

@keyframes slideInUp {
from {
transform: translate3d(0, 100%, 0);
visibility: visible;
}

to {
transform: translate3d(0, 0, 0);
}
}
}
109 changes: 56 additions & 53 deletions src/app/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,74 +47,74 @@
}

:root {
--radius: 0.625rem;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--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: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--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%);
--link: oklch(1 0 89.876 / 0);
--link-foreground: oklch(54.65% 0.246 262.87);
}

.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--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: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
--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%);
--link: oklch(1 0 89.876 / 0);
--link-foreground: oklch(54.65% 0.246 262.87);
}
Expand All @@ -126,4 +126,7 @@
body {
@apply bg-background text-foreground;
}
.input-max-w {
@apply md:max-w-[380px];
}
}
25 changes: 25 additions & 0 deletions src/entities/file/api/http.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { api } from 'shared/api';
import { UploadFileData, UploadResponse } from '../model/types';
import { UploadResponse as UploadResponseSchema } from '../model/schemas';

export class UploadHttp {
static uploadFile(data: UploadFileData): Promise<UploadResponse> {
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>({
url: '/upload',
method: 'POST',
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
contracts: {
response: UploadResponseSchema,
},
});
}
}
3 changes: 3 additions & 0 deletions src/entities/file/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * as SFile from './model/schemas';
export * as TFile from './model/types';
export { UploadHttp } from './api/http';
3 changes: 3 additions & 0 deletions src/entities/file/model/schemas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { GlobalSuccess } from 'shared/api';

export const UploadResponse = GlobalSuccess;
9 changes: 9 additions & 0 deletions src/entities/file/model/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod/v4';
import { UploadResponse } from './schemas';

export type UploadResponse = z.infer<typeof UploadResponse>;

export type UploadFileData = {
file: File;
context: string; //TODO: typify
};
Loading
Loading