Skip to content

Commit 1f481da

Browse files
profile settings
1 parent 3d994c5 commit 1f481da

File tree

11 files changed

+250
-93
lines changed

11 files changed

+250
-93
lines changed

packages/web/src/app/(app)/@sidebar/components/settingsSidebar/nav.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
PlugIcon,
1818
ScrollTextIcon,
1919
ShieldIcon,
20+
UserIcon,
2021
UsersIcon,
2122
} from "lucide-react";
2223
import Link from "next/link";
@@ -30,6 +31,7 @@ const iconMap = {
3031
"plug": PlugIcon,
3132
"chart-area": ChartAreaIcon,
3233
"scroll-text": ScrollTextIcon,
34+
"user": UserIcon,
3335
} satisfies Record<string, LucideIcon>;
3436

3537
export type NavIconName = keyof typeof iconMap;

packages/web/src/app/(app)/@sidebar/components/sidebarBase.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const MeControlDropdownMenu = ({
158158
<ChevronsUpDown className="ml-auto size-4" />
159159
</SidebarMenuButton>
160160
</DropdownMenuTrigger>
161-
<DropdownMenuContent className="w-64" side="top" align="start" sideOffset={4}>
161+
<DropdownMenuContent className="w-64" side="right" align="end" sideOffset={4}>
162162
<DropdownMenuGroup>
163163
<div className="flex flex-row items-center gap-3 px-3 py-3">
164164
<UserAvatar
@@ -224,7 +224,7 @@ const GuestDropdownMenu = () => {
224224
<ChevronsUpDown className="ml-auto size-4" />
225225
</SidebarMenuButton>
226226
</DropdownMenuTrigger>
227-
<DropdownMenuContent className="w-64" side="top" align="start" sideOffset={4}>
227+
<DropdownMenuContent className="w-64" side="right" align="end" sideOffset={4}>
228228
<AppearanceDropdownMenuGroup />
229229
<DropdownMenuSeparator />
230230
<DropdownMenuItem asChild>
@@ -288,13 +288,13 @@ const AppearanceDropdownMenuGroup = () => {
288288
<DropdownMenuSub>
289289
<DropdownMenuSubTrigger>
290290
<CodeIcon className="h-4 w-4 mr-2" />
291-
<span>Code navigation</span>
291+
<span>Editor keymap</span>
292292
</DropdownMenuSubTrigger>
293293
<DropdownMenuPortal>
294294
<DropdownMenuSubContent>
295295
<DropdownMenuRadioGroup value={keymapType} onValueChange={(value) => setKeymapType(value as KeymapType)}>
296296
<DropdownMenuRadioItem value="default">
297-
Default
297+
Standard
298298
</DropdownMenuRadioItem>
299299
<DropdownMenuRadioItem value="vim">
300300
Vim

packages/web/src/app/(app)/settings/apiKeys/apiKeysPage.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
AlertDialogTrigger,
1717
} from "@/components/ui/alert-dialog";
1818
import { isServiceError } from "@/lib/utils";
19-
import { Copy, Check, AlertTriangle, Loader2, KeyRound, Trash2 } from "lucide-react";
19+
import { Copy, Check, AlertTriangle, Loader2, KeyRound, Plus, Trash2 } from "lucide-react";
2020
import { useMemo, useState } from "react";
2121
import { useToast } from "@/components/hooks/use-toast";
2222
import useCaptureEvent from "@/hooks/useCaptureEvent";
@@ -136,11 +136,11 @@ export function ApiKeysPage({ canCreateApiKey, apiKeys }: ApiKeysPageProps) {
136136
<div>
137137
<h3 className="text-lg font-medium">API Keys</h3>
138138
<p className="text-sm text-muted-foreground">
139-
Create and manage API keys for programmatic access to Sourcebot. All API keys are scoped to the user who created them.
139+
Create and manage API keys for programmatic access to Sourcebot on your behalf.
140140
</p>
141141
</div>
142142

143-
<div className="border border-border rounded-lg">
143+
<div className="border border-border rounded-lg bg-card">
144144
<div className="flex items-center justify-between px-4 py-3">
145145
<span className="text-sm text-muted-foreground">
146146
{apiKeys.length} API key{apiKeys.length !== 1 ? "s" : ""}
@@ -168,6 +168,7 @@ export function ApiKeysPage({ canCreateApiKey, apiKeys }: ApiKeysPageProps) {
168168
setIsCreateDialogOpen(true);
169169
}}
170170
>
171+
<Plus className="h-4 w-4" />
171172
New API key
172173
</Button>
173174
</DialogTrigger>
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"use client";
2+
3+
import { Separator } from "@/components/ui/separator";
4+
import { createContext, ReactNode, useContext, Children } from "react";
5+
6+
const SettingsCardGroupContext = createContext(false);
7+
8+
interface SettingsCardProps {
9+
children: ReactNode;
10+
}
11+
12+
export function SettingsCard({ children }: SettingsCardProps) {
13+
const isInGroup = useContext(SettingsCardGroupContext);
14+
15+
if (isInGroup) {
16+
return <div className="p-4">{children}</div>;
17+
}
18+
19+
return (
20+
<div className="rounded-lg border border-border bg-card p-4">
21+
{children}
22+
</div>
23+
);
24+
}
25+
26+
interface BasicSettingsCardProps {
27+
name: string;
28+
description: string;
29+
children: ReactNode;
30+
}
31+
32+
export function BasicSettingsCard({ name, description, children }: BasicSettingsCardProps) {
33+
return (
34+
<SettingsCard>
35+
<div className="flex items-start justify-between gap-4">
36+
<div className="flex-1 min-w-0">
37+
<p className="font-medium">{name}</p>
38+
<p className="text-sm text-muted-foreground mt-1">{description}</p>
39+
</div>
40+
<div className="flex-shrink-0">
41+
{children}
42+
</div>
43+
</div>
44+
</SettingsCard>
45+
);
46+
}
47+
48+
interface SettingsCardGroupProps {
49+
children: ReactNode;
50+
}
51+
52+
export function SettingsCardGroup({ children }: SettingsCardGroupProps) {
53+
const childArray = Children.toArray(children);
54+
55+
return (
56+
<SettingsCardGroupContext.Provider value={true}>
57+
<div className="rounded-lg border border-border bg-card">
58+
{childArray.map((child, index) => (
59+
<div key={index}>
60+
{child}
61+
{index < childArray.length - 1 && <Separator />}
62+
</div>
63+
))}
64+
</div>
65+
</SettingsCardGroupContext.Provider>
66+
);
67+
}

packages/web/src/app/(app)/settings/layout.tsx

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,25 @@ export const getSidebarNavGroups = async () =>
6161
{
6262
label: "Account",
6363
items: [
64-
...(hasEntitlement("sso") ? [
65-
{
66-
title: "Linked Accounts",
67-
href: `/settings/linked-accounts`,
68-
icon: "link" as const,
69-
}
70-
] : []),
64+
{
65+
title: "Profile",
66+
href: `/settings/profile`,
67+
icon: "user" as const,
68+
},
7169
...(env.DISABLE_API_KEY_USAGE_FOR_NON_OWNER_USERS === 'false' || role === OrgRole.OWNER ? [
7270
{
7371
title: "API Keys",
7472
href: `/settings/apiKeys`,
7573
icon: "key-round" as const,
7674
}
7775
] : []),
76+
...(hasEntitlement("sso") ? [
77+
{
78+
title: "Linked Accounts",
79+
href: `/settings/linked-accounts`,
80+
icon: "link" as const,
81+
}
82+
] : []),
7883
],
7984
},
8085
];

packages/web/src/app/(app)/settings/linked-accounts/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { isServiceError } from "@/lib/utils";
44
import { LogoutEscapeHatch } from "@/app/components/logoutEscapeHatch";
55
import { ShieldCheck } from "lucide-react";
66
import { LinkedAccountProviderCard } from "@/ee/features/sso/components/linkedAccountProviderCard";
7+
import { SettingsCardGroup } from "../components/settingsCard";
78

89
export default async function LinkedAccountsPage() {
910
const linkedAccounts = await getLinkedAccounts();
@@ -43,7 +44,7 @@ export default async function LinkedAccountsPage() {
4344
</CardContent>
4445
</Card>
4546
) : (
46-
<div className="space-y-4">
47+
<div className="space-y-3">
4748
{linkedAccounts
4849
.sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0))
4950
.map((account) => (
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { authenticatedPage } from "@/middleware/authenticatedPage";
2+
import { ProfilePage } from "./profilePage";
3+
4+
export default authenticatedPage(async () => {
5+
return <ProfilePage />;
6+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use client';
2+
3+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
4+
import { useKeymapType } from "@/hooks/useKeymapType";
5+
import { KeymapType } from "@/lib/types";
6+
import { Laptop, Moon, Sun, Terminal, TextCursor } from "lucide-react";
7+
import { useTheme } from "next-themes";
8+
import { useMemo } from "react";
9+
import { BasicSettingsCard, SettingsCardGroup } from "../components/settingsCard";
10+
11+
const themeOptions = [
12+
{ value: "light", label: "Light", icon: Sun },
13+
{ value: "dark", label: "Dark", icon: Moon },
14+
{ value: "system", label: "System", icon: Laptop },
15+
] as const;
16+
17+
const keymapOptions = [
18+
{ value: "default", label: "Standard", icon: TextCursor },
19+
{ value: "vim", label: "Vim", icon: Terminal },
20+
] as const;
21+
22+
export function ProfilePage() {
23+
const { theme: _theme, setTheme } = useTheme();
24+
const [keymapType, setKeymapType] = useKeymapType();
25+
26+
const theme = useMemo(() => {
27+
return _theme ?? "light";
28+
}, [_theme]);
29+
30+
return (
31+
<div className="flex flex-col gap-6">
32+
<div>
33+
<h3 className="text-lg font-medium">Profile</h3>
34+
<p className="text-sm text-muted-foreground">
35+
Manage your account preferences and profile settings.
36+
</p>
37+
</div>
38+
<SettingsCardGroup>
39+
<BasicSettingsCard name="Appearance" description="Choose how Sourcebot looks to you.">
40+
<Select value={theme} onValueChange={setTheme}>
41+
<SelectTrigger className="w-36">
42+
<SelectValue />
43+
</SelectTrigger>
44+
<SelectContent>
45+
{themeOptions.map((option) => (
46+
<SelectItem key={option.value} value={option.value}>
47+
<div className="flex items-center gap-2">
48+
<option.icon className="h-3 w-3" />
49+
<span>{option.label}</span>
50+
</div>
51+
</SelectItem>
52+
))}
53+
</SelectContent>
54+
</Select>
55+
</BasicSettingsCard>
56+
<BasicSettingsCard name="Editor keymap" description="Choose the keyboard shortcuts used in the code viewer.">
57+
<Select value={keymapType} onValueChange={(value) => setKeymapType(value as KeymapType)}>
58+
<SelectTrigger className="w-36">
59+
<SelectValue />
60+
</SelectTrigger>
61+
<SelectContent>
62+
{keymapOptions.map((option) => (
63+
<SelectItem key={option.value} value={option.value}>
64+
<div className="flex items-center gap-2">
65+
<option.icon className="h-3 w-3" />
66+
<span>{option.label}</span>
67+
</div>
68+
</SelectItem>
69+
))}
70+
</SelectContent>
71+
</Select>
72+
</BasicSettingsCard>
73+
</SettingsCardGroup>
74+
</div>
75+
);
76+
}

packages/web/src/app/globals.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
--background: hsl(0, 0%, 6%);
118118
--background-secondary: hsl(222.2 84% 4.9%);
119119
--foreground: hsl(210 40% 98%);
120-
--card: hsl(222.2 84% 4.9%);
120+
--card: hsl(221, 89%, 4%);
121121
--card-foreground: hsl(210 40% 98%);
122122
--popover: hsl(222.2 84% 4.9%);
123123
--popover-foreground: hsl(210 40% 98%);

packages/web/src/ee/features/sso/components/connectAccountsCard.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4-
import { Button } from "@/components/ui/button";
4+
import { LoadingButton } from "@/components/ui/loading-button";
55
import { skipOptionalProvidersLink } from "@/ee/features/sso/actions";
66
import { useRouter } from "next/navigation";
77
import { useState } from "react";
@@ -21,11 +21,10 @@ export const ConnectAccountsCard = ({ linkedAccounts, callbackUrl }: ConnectAcco
2121
setIsSkipping(true);
2222
try {
2323
await skipOptionalProvidersLink();
24+
router.refresh();
2425
} catch (error) {
2526
console.error("Failed to skip optional providers:", error);
26-
} finally {
2727
setIsSkipping(false);
28-
router.refresh()
2928
}
3029
};
3130

@@ -43,27 +42,27 @@ export const ConnectAccountsCard = ({ linkedAccounts, callbackUrl }: ConnectAcco
4342
You can manage your linked accounts later in <strong>Settings → Linked Accounts.</strong>
4443
</CardDescription>
4544
</CardHeader>
46-
<CardContent className="space-y-4">
47-
<div className="space-y-2">
45+
<CardContent>
46+
<div className="space-y-3">
4847
{accountLinkingProviders
4948
.sort((a, b) => (b.required ? 1 : 0) - (a.required ? 1 : 0))
5049
.map(account => (
51-
<LinkedAccountProviderCard
52-
key={account.provider}
53-
linkedAccount={account}
54-
callbackUrl={callbackUrl}
55-
/>
56-
))}
50+
<LinkedAccountProviderCard
51+
key={account.provider}
52+
linkedAccount={account}
53+
callbackUrl={callbackUrl}
54+
/>
55+
))}
5756
</div>
5857
{canSkip && (
59-
<Button
58+
<LoadingButton
6059
variant="outline"
61-
className="w-full"
60+
className="w-full mt-5"
6261
onClick={handleSkip}
63-
disabled={isSkipping}
62+
loading={isSkipping}
6463
>
65-
{isSkipping ? "Skipping..." : "Skip for now"}
66-
</Button>
64+
Skip for now
65+
</LoadingButton>
6766
)}
6867
</CardContent>
6968
</Card>

0 commit comments

Comments
 (0)