Skip to content

Commit a4f1b2c

Browse files
PengyuChen01pengyuautofix-ci[bot]NarwhalChen
authored
feat(frontend): css optimization in frontend (#156)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Refactor** - Streamlined the project creation process for a smoother workflow. - **Style** - Redesigned sidebar elements with refined spacing and improved responsiveness. - **New Features** - Introduced new account management capabilities, including email confirmation and project limit querying. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: pengyu <pengyuchen01@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: NarwhalChen <125920907+NarwhalChen@users.noreply.github.com>
1 parent 4ca3a2c commit a4f1b2c

File tree

5 files changed

+103
-86
lines changed

5 files changed

+103
-86
lines changed

frontend/src/app/(main)/page.tsx

+2-10
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,8 @@ export default function HomePage() {
2929
if (!message.trim()) return;
3030

3131
try {
32-
// Create the project
33-
const result = await createProjectFromPrompt(message, isPublic, model);
34-
35-
// If successful, clear the input
36-
if (result) {
37-
promptFormRef.current.clearMessage();
38-
39-
// Note: No need to navigate here as the ProjectContext's onCompleted handler
40-
// in the createProject mutation will handle navigation to the chat page
41-
}
32+
await createProjectFromPrompt(message, isPublic, model);
33+
promptFormRef.current.clearMessage();
4234
} catch (error) {
4335
console.error('Error creating project:', error);
4436
}

frontend/src/components/sidebar-item.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ export function SideBarItem({
104104
buttonVariants({
105105
variant,
106106
}),
107-
'flex justify-between w-full h-14 text-base font-normal items-center group'
107+
'relative flex w-full h-14 text-base font-normal items-center group px-2'
108108
)}
109109
onClick={handleChatClick}
110110
>
111-
<div className="flex-1 flex gap-3 items-center truncate ml-2">
111+
<div className="flex-1 flex items-center truncate ml-2 mr-12 min-w-0">
112112
<div className="flex flex-col">
113113
<span className="text-xs font-normal">{title || 'New Chat'}</span>
114114
</div>
@@ -119,7 +119,7 @@ export function SideBarItem({
119119
<DropdownMenuTrigger asChild>
120120
<Button
121121
variant="ghost"
122-
className="flex justify-end items-center dropdown-trigger mr-2"
122+
className="absolute right-2 top-1/2 transform -translate-y-1/2 w-10 h-10 rounded-md hover:bg-gray-200"
123123
onClick={(e) => {
124124
e.preventDefault();
125125
e.stopPropagation();

frontend/src/components/sidebar.tsx

+80-68
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
'use client';
2+
23
import { Button } from '@/components/ui/button';
34
import Image from 'next/image';
45
import { memo, useCallback, useContext, useState } from 'react';
5-
import { SquarePen } from 'lucide-react';
66
import SidebarSkeleton from './sidebar-skeleton';
77
import UserSettings from './user-settings';
88
import { SideBarItem } from './sidebar-item';
@@ -22,9 +22,9 @@ import {
2222
import { ProjectContext } from './chat/code-engine/project-context';
2323

2424
interface SidebarProps {
25-
setIsModalOpen: (value: boolean) => void; // Parent setter to update collapse state
25+
setIsModalOpen: (value: boolean) => void;
2626
isCollapsed: boolean;
27-
setIsCollapsed: (value: boolean) => void; // Parent setter to update collapse state
27+
setIsCollapsed: (value: boolean) => void;
2828
isMobile: boolean;
2929
currentChatId?: string;
3030
chatListUpdated: boolean;
@@ -44,11 +44,10 @@ export function ChatSideBar({
4444
error,
4545
onRefetch,
4646
}: SidebarProps) {
47-
// Use a local state only for the currently selected chat.
4847
const router = useRouter();
4948
const [currentChatid, setCurrentChatid] = useState('');
5049
const { setCurProject, pollChatProject } = useContext(ProjectContext);
51-
// Handler for starting a new chat.
50+
5251
const handleNewChat = useCallback(() => {
5352
window.history.replaceState({}, '', '/');
5453
setCurrentChatid('');
@@ -64,86 +63,97 @@ export function ChatSideBar({
6463
return (
6564
<div
6665
data-collapsed={isCollapsed}
67-
className="relative justify-between group lg:bg-accent/0 lg:dark:bg-card/0 flex flex-col h-full"
66+
className="relative flex flex-col h-full justify-between group lg:bg-accent/0 lg:dark:bg-card/0"
6867
>
6968
<Sidebar collapsible="icon" side="left">
70-
{/* Toggle button: Clicking this will toggle the collapse state */}
71-
<SidebarTrigger
72-
className="lg:flex items-center justify-center cursor-pointer p-2 ml-3.5 mt-2"
73-
onClick={() => setIsCollapsed(!isCollapsed)}
74-
/>
75-
<Button
76-
onClick={() => router.push('/')}
77-
variant="ghost"
78-
className="
79-
w-full
80-
h-14
81-
flex
82-
items-center
83-
justify-start
84-
px-4
85-
gap-2
86-
text-sm
87-
xl:text-lg
88-
font-normal
89-
rounded-md
90-
hover:bg-yellow-50
91-
transition-all
92-
duration-200
93-
ease-in-out
94-
"
69+
{/* Header Row: Fox Logo (clickable) on the left, SidebarTrigger on the right */}
70+
<div
71+
className={`flex items-center ${isCollapsed ? 'justify-center w-full px-0' : 'justify-between px-3'} pt-3`}
9572
>
96-
<Image
97-
src="/codefox.svg"
98-
alt="CodeFox Logo"
99-
width={32}
100-
height={32}
101-
className="flex-shrink-0 dark:invert"
102-
/>
10373
{!isCollapsed && (
104-
<span className="text-primary-500 font-semibold text-lg">
105-
CodeFox
106-
</span>
74+
<div className="flex flex-1 items-center justify-between">
75+
<Button
76+
onClick={() => router.push('/')}
77+
variant="ghost"
78+
className="inline-flex items-center gap-2 pl-0
79+
rounded-md ease-in-out"
80+
>
81+
<Image
82+
src="/codefox.svg"
83+
alt="CodeFox Logo"
84+
width={40}
85+
height={40}
86+
className="dark:invert"
87+
/>
88+
<span className="text-primary-500 font-semibold text-base">
89+
CodeFox
90+
</span>
91+
</Button>
92+
93+
{/* SidebarTrigger 保证在 CodeFox 按钮的中间 */}
94+
<SidebarTrigger
95+
className="flex items-center justify-center w-12 h-12 "
96+
onClick={() => setIsCollapsed(!isCollapsed)}
97+
/>
98+
</div>
99+
)}
100+
101+
{isCollapsed && (
102+
<SidebarTrigger
103+
className="flex items-center justify-center w-full p-2 mt"
104+
onClick={() => setIsCollapsed(!isCollapsed)}
105+
/>
107106
)}
108-
</Button>
107+
</div>
109108

110109
{/* Divider Line */}
111110
<div className="border-t border-dotted border-gray-300 my-2 w-full mx-auto" />
112111

113-
<Button
114-
onClick={() => setIsModalOpen(true)}
115-
size="setting"
116-
variant="ghost"
117-
className="flex items-center justify-start w-[85%] h-14 text-xs xl:text-sm font-normal gap-2 pl-4 hover:bg-yellow-50 rounded-md transition-all duration-200 ease-in-out"
112+
{/* New Project 按钮 - 依然占据整行 */}
113+
<div
114+
className={`flex ${isCollapsed ? 'justify-center items-center w-full px-0' : ''} w-full mt-4`}
118115
>
119-
<div className="flex items-center gap-2">
116+
<Button
117+
onClick={() => {
118+
if (isCollapsed) {
119+
router.push('/');
120+
} else {
121+
setIsModalOpen(true);
122+
}
123+
}}
124+
variant="ghost"
125+
className={`h-7 w-7 flex items-center justify-center rounded-md ease-in-out ${
126+
!isCollapsed && 'w-full gap-2 pl-4 justify-start'
127+
}`}
128+
>
120129
<svg
130+
data-name="Layer 1"
131+
viewBox="0 0 32 32"
132+
preserveAspectRatio="xMidYMid meet"
121133
xmlns="http://www.w3.org/2000/svg"
122-
fill="none"
123-
viewBox="0 0 24 24"
124-
strokeWidth="1.5"
125-
stroke="currentColor"
126-
className="w-5 h-5 text-yellow-500"
134+
className={
135+
isCollapsed
136+
? 'w-8 h-8 min-w-[32px] min-h-[32px] ml-[-5px] mt-[-10px]'
137+
: 'w-10 h-10 min-w-[32px] min-h-[32px] mr-1'
138+
}
139+
strokeWidth="0.1"
127140
>
128-
<path
129-
strokeLinecap="round"
130-
strokeLinejoin="round"
131-
d="M12 18v-5.25m0 0a6.01 6.01 0 0 0 1.5-.189m-1.5.189a6.01 6.01 0 0 1-1.5-.189m3.75 7.478a12.06 12.06 0 0 1-4.5 0m3.75 2.383a14.406 14.406 0 0 1-3 0M14.25 18v-.192c0-.983.658-1.823 1.508-2.316a7.5 7.5 0 1 0-7.517 0c.85.493 1.509 1.333 1.509 2.316V18"
132-
/>
141+
<g transform="scale(-1,1) translate(-32,0)">
142+
<path
143+
d="M5,8A1,1,0,0,0,7,8V7H8A1,1,0,0,0,8,5H7V4A1,1,0,0,0,5,4V5H4A1,1,0,0,0,4,7H5ZM18,5H12a1,1,0,0,0,0,2h6a1,1,0,0,1,1,1v9.72l-1.57-1.45a1,1,0,0,0-.68-.27H8a1,1,0,0,1-1-1V12a1,1,0,0,0-2,0v3a3,3,0,0,0,3,3h8.36l3,2.73A1,1,0,0,0,20,21a1.1,1.1,0,0,0,.4-.08A1,1,0,0,0,21,20V8A3,3,0,0,0,18,5Z"
144+
fill="#808080"
145+
/>
146+
</g>
133147
</svg>
134-
135148
{!isCollapsed && (
136-
<span className="text-primary-600 hover:text-primary-800 transition-colors text-sm">
149+
<span className="text-gray-600 hover:text-gray-800 font-semibold text-sm relative -top-0.5">
137150
New Project
138151
</span>
139152
)}
153+
</Button>
154+
</div>
140155

141-
{!isCollapsed && (
142-
<SquarePen className="text-primary-400 hover:text-primary-600 transition-colors w-4 h-4" />
143-
)}
144-
</div>
145-
</Button>
146-
156+
{/* 聊天列表 */}
147157
<SidebarContent>
148158
<SidebarGroup>
149159
<SidebarGroupContent>
@@ -171,12 +181,14 @@ export function ChatSideBar({
171181
</SidebarGroup>
172182
</SidebarContent>
173183

174-
<SidebarFooter>
184+
{/* 底部设置 */}
185+
<SidebarFooter
186+
className={`mt-auto ${isCollapsed ? 'flex justify-center px-0' : ''}`}
187+
>
175188
<UserSettings isSimple={false} />
176189
</SidebarFooter>
177190

178191
<SidebarRail
179-
// Optional: Provide a secondary trigger if needed.
180192
setIsSimple={() => setIsCollapsed(!isCollapsed)}
181193
isSimple={false}
182194
/>

frontend/src/graphql/schema.gql

+16-5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ input CreateProjectInput {
5757
"""Date custom scalar type"""
5858
scalar Date
5959

60+
type EmailConfirmationResponse {
61+
message: String!
62+
success: Boolean
63+
}
64+
6065
input FetchPublicProjectsInputs {
6166
size: Float!
6267
strategy: String!
@@ -101,6 +106,7 @@ type Message {
101106

102107
type Mutation {
103108
clearChatHistory(chatId: String!): Boolean!
109+
confirmEmail(token: String!): EmailConfirmationResponse!
104110
createChat(newChatInput: NewChatInput!): Chat!
105111
createProject(createProjectInput: CreateProjectInput!): Chat!
106112
deleteChat(chatId: String!): Boolean!
@@ -110,6 +116,7 @@ type Mutation {
110116
refreshToken(refreshToken: String!): RefreshTokenResponse!
111117
regenerateDescription(input: String!): String!
112118
registerUser(input: RegisterUserInput!): User!
119+
resendConfirmationEmail(input: ResendEmailInput!): EmailConfirmationResponse!
113120
subscribeToProject(projectId: ID!): Project!
114121
triggerChatStream(input: ChatInputType!): Boolean!
115122
updateChatTitle(updateChatTitleInput: UpdateChatTitleInput!): Chat
@@ -137,9 +144,7 @@ type Project {
137144
projectPath: String!
138145
subNumber: Float!
139146

140-
"""
141-
Projects that are copies of this project
142-
"""
147+
"""Projects that are copies of this project"""
143148
subscribers: [Project!]
144149
uniqueProjectId: String!
145150
updatedAt: Date!
@@ -171,6 +176,7 @@ type Query {
171176
getChatHistory(chatId: String!): [Message!]!
172177
getHello: String!
173178
getProject(projectId: String!): Project!
179+
getRemainingProjectLimit: Int!
174180
getSubscribedProjects: [Project!]!
175181
getUserChats: [Chat!]
176182
getUserProjects: [Project!]!
@@ -190,6 +196,10 @@ input RegisterUserInput {
190196
username: String!
191197
}
192198

199+
input ResendEmailInput {
200+
email: String!
201+
}
202+
193203
enum Role {
194204
Assistant
195205
System
@@ -225,9 +235,10 @@ type User {
225235
id: ID!
226236
isActive: Boolean!
227237
isDeleted: Boolean!
238+
isEmailConfirmed: Boolean!
239+
lastEmailSendTime: Date!
228240
projects: [Project!]!
229-
subscribedProjects: [Project!]
230-
@deprecated(reason: "Use projects with forkedFromId instead")
241+
subscribedProjects: [Project!] @deprecated(reason: "Use projects with forkedFromId instead")
231242
updatedAt: Date!
232243
username: String!
233244
}

frontend/src/providers/AuthProvider.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
152152

153153
let isValid = await validateToken();
154154

155+
// 如果验证失败,再试图刷新
155156
if (!isValid) {
156157
isValid = (await refreshAccessToken()) ? true : false;
157158
}
158159

160+
// 最终判断
159161
if (isValid) {
160162
setIsAuthorized(true);
161163
await fetchUserInfo();

0 commit comments

Comments
 (0)