kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(web): sync locale with auth session and account preferences

Tin d2339ffe 86033857

+136 -35
+6 -1
apps/web/src/components/providers/auth-provider/index.tsx
··· 21 21 } 22 22 23 23 return ( 24 - <AuthContext.Provider value={{ user: data?.user, isLoading: isPending }}> 24 + <AuthContext.Provider 25 + value={{ 26 + user: (data?.user as User | null | undefined) ?? null, 27 + isLoading: isPending, 28 + }} 29 + > 25 30 {children} 26 31 </AuthContext.Provider> 27 32 );
+4 -2
apps/web/src/hooks/mutations/use-update-user-profile.ts
··· 2 2 import { authClient } from "@/lib/auth-client"; 3 3 4 4 type UpdateUserProfileRequest = { 5 - name: string; 5 + name?: string; 6 + locale?: string; 6 7 }; 7 8 8 9 function useUpdateUserProfile() { 9 10 return useMutation({ 10 - mutationFn: async ({ name }: UpdateUserProfileRequest) => { 11 + mutationFn: async ({ name, locale }: UpdateUserProfileRequest) => { 11 12 const { data, error } = await authClient.updateUser({ 12 13 name, 14 + locale, 13 15 }); 14 16 15 17 if (error) {
+10
apps/web/src/lib/auth-client.ts
··· 3 3 anonymousClient, 4 4 emailOTPClient, 5 5 genericOAuthClient, 6 + inferAdditionalFields, 6 7 lastLoginMethodClient, 7 8 magicLinkClient, 8 9 organizationClient, ··· 38 39 }), 39 40 genericOAuthClient(), 40 41 apiKeyClient(), 42 + inferAdditionalFields({ 43 + user: { 44 + locale: { 45 + type: "string", 46 + required: false, 47 + input: true, 48 + }, 49 + }, 50 + }), 41 51 ], 42 52 });
+111 -31
apps/web/src/routes/_layout/_authenticated/dashboard/settings/account/preferences.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { LayoutGrid, List, RotateCcw } from "lucide-react"; 3 + import { useTranslation } from "react-i18next"; 3 4 import { Button } from "@/components/ui/button"; 4 5 import { Label } from "@/components/ui/label"; 5 6 import { ··· 11 12 } from "@/components/ui/select"; 12 13 import { Separator } from "@/components/ui/separator"; 13 14 import { Switch } from "@/components/ui/switch"; 15 + import { useLocale } from "@/hooks/use-locale"; 14 16 import { useUserPreferencesStore } from "@/store/user-preferences"; 15 17 16 18 export const Route = createFileRoute( ··· 20 22 }); 21 23 22 24 function RouteComponent() { 25 + const { t } = useTranslation(); 26 + const { locale, setLocale } = useLocale(); 23 27 const { 24 28 theme, 25 29 setTheme, ··· 45 49 return ( 46 50 <div className="max-w-4xl mx-auto space-y-8"> 47 51 <div className="space-y-2"> 48 - <h1 className="text-2xl font-semibold">Preferences</h1> 52 + <h1 className="text-2xl font-semibold"> 53 + {t("settings:preferencesPage.title")} 54 + </h1> 49 55 <p className="text-muted-foreground"> 50 - Customize your Kaneo experience. 56 + {t("settings:preferencesPage.subtitle")} 51 57 </p> 52 58 </div> 53 59 54 60 <div className="space-y-6"> 55 61 <div className="space-y-1"> 56 - <h2 className="text-md font-medium">Appearance</h2> 62 + <h2 className="text-md font-medium"> 63 + {t("settings:preferencesPage.appearanceTitle")} 64 + </h2> 57 65 <p className="text-xs text-muted-foreground"> 58 - Visual settings and layout preferences. 66 + {t("settings:preferencesPage.appearanceSubtitle")} 59 67 </p> 60 68 </div> 61 69 62 70 <div className="space-y-4 border border-border rounded-md p-4 bg-sidebar"> 63 71 <div className="flex items-center justify-between"> 64 72 <div className="space-y-0.5"> 65 - <Label className="text-sm font-medium">Theme</Label> 73 + <Label className="text-sm font-medium"> 74 + {t("settings:preferencesPage.theme")} 75 + </Label> 66 76 <p className="text-xs text-muted-foreground"> 67 - Choose your preferred color scheme 77 + {t("settings:preferencesPage.themeDescription")} 68 78 </p> 69 79 </div> 70 80 <Select ··· 72 82 onValueChange={(value) => value && setTheme(value)} 73 83 > 74 84 <SelectTrigger className="!py-4"> 75 - <SelectValue placeholder="Select a theme" /> 85 + <SelectValue 86 + placeholder={t("settings:preferencesPage.selectTheme")} 87 + /> 76 88 </SelectTrigger> 77 89 <SelectContent> 78 90 <SelectItem value="light"> ··· 83 95 Aa 84 96 </span> 85 97 </div> 86 - <span className="text-xs font-normal">Light</span> 98 + <span className="text-xs font-normal"> 99 + {t("settings:preferencesPage.themeLight")} 100 + </span> 87 101 </div> 88 102 </SelectItem> 89 103 <SelectItem value="dark"> ··· 94 108 Aa 95 109 </span> 96 110 </div> 97 - <span className="text-xs font-normal">Dark</span> 111 + <span className="text-xs font-normal"> 112 + {t("settings:preferencesPage.themeDark")} 113 + </span> 98 114 </div> 99 115 </SelectItem> 116 + <SelectItem value="system"> 117 + <span className="text-xs font-normal"> 118 + {t("settings:preferencesPage.themeSystem")} 119 + </span> 120 + </SelectItem> 100 121 </SelectContent> 101 122 </Select> 102 123 </div> ··· 105 126 106 127 <div className="flex items-center justify-between"> 107 128 <div className="space-y-0.5"> 108 - <Label className="text-sm font-medium">Default View</Label> 129 + <Label className="text-sm font-medium"> 130 + {t("settings:preferencesPage.language")} 131 + </Label> 109 132 <p className="text-xs text-muted-foreground"> 110 - Choose your preferred task view mode 133 + {t("settings:preferencesPage.languageDescription")} 134 + </p> 135 + </div> 136 + <Select 137 + value={locale ?? "en-US"} 138 + onValueChange={(value) => { 139 + if (value) { 140 + void setLocale(value); 141 + } 142 + }} 143 + > 144 + <SelectTrigger className="!py-4"> 145 + <SelectValue 146 + placeholder={t("settings:preferencesPage.selectLanguage")} 147 + /> 148 + </SelectTrigger> 149 + <SelectContent> 150 + <SelectItem value="en-US"> 151 + {t("common:language.english")} 152 + </SelectItem> 153 + <SelectItem value="de-DE"> 154 + {t("common:language.german")} 155 + </SelectItem> 156 + </SelectContent> 157 + </Select> 158 + </div> 159 + 160 + <Separator /> 161 + 162 + <div className="flex items-center justify-between"> 163 + <div className="space-y-0.5"> 164 + <Label className="text-sm font-medium"> 165 + {t("settings:preferencesPage.defaultView")} 166 + </Label> 167 + <p className="text-xs text-muted-foreground"> 168 + {t("settings:preferencesPage.defaultViewDescription")} 111 169 </p> 112 170 </div> 113 171 <Select ··· 115 173 onValueChange={(value) => value && setViewMode(value)} 116 174 > 117 175 <SelectTrigger className="!py-4"> 118 - <SelectValue placeholder="Select a view mode" /> 176 + <SelectValue 177 + placeholder={t("settings:preferencesPage.selectViewMode")} 178 + /> 119 179 </SelectTrigger> 120 180 <SelectContent> 121 181 <SelectItem value="board"> 122 182 <div className="flex items-center gap-3"> 123 183 <LayoutGrid className="h-4 w-4 mr-1" /> 124 - <span className="text-xs font-normal">Board</span> 184 + <span className="text-xs font-normal"> 185 + {t("settings:preferencesPage.board")} 186 + </span> 125 187 </div> 126 188 </SelectItem> 127 189 <SelectItem value="list"> 128 190 <div className="flex items-center gap-3"> 129 191 <List className="h-4 w-4 mr-1" /> 130 - <span className="text-xs font-normal">List</span> 192 + <span className="text-xs font-normal"> 193 + {t("settings:preferencesPage.list")} 194 + </span> 131 195 </div> 132 196 </SelectItem> 133 197 </SelectContent> ··· 138 202 139 203 <div className="flex items-center justify-between"> 140 204 <div className="space-y-0.5"> 141 - <Label className="text-sm font-medium">Compact Mode</Label> 205 + <Label className="text-sm font-medium"> 206 + {t("settings:preferencesPage.compactMode")} 207 + </Label> 142 208 <p className="text-xs text-muted-foreground"> 143 - Use reduced spacing for more content 209 + {t("settings:preferencesPage.compactModeDescription")} 144 210 </p> 145 211 </div> 146 212 <Switch checked={compactMode} onCheckedChange={setCompactMode} /> ··· 150 216 151 217 <div className="flex items-center justify-between"> 152 218 <div className="space-y-0.5"> 153 - <Label className="text-sm font-medium">Sidebar Default</Label> 219 + <Label className="text-sm font-medium"> 220 + {t("settings:preferencesPage.sidebarDefault")} 221 + </Label> 154 222 <p className="text-xs text-muted-foreground"> 155 - Keep sidebar expanded on startup 223 + {t("settings:preferencesPage.sidebarDefaultDescription")} 156 224 </p> 157 225 </div> 158 226 <Switch ··· 166 234 <div className="space-y-6"> 167 235 <div className="flex items-center justify-between"> 168 236 <div className="space-y-1"> 169 - <h2 className="text-md font-medium">Display options</h2> 237 + <h2 className="text-md font-medium"> 238 + {t("settings:preferencesPage.displayOptions")} 239 + </h2> 170 240 <p className="text-xs text-muted-foreground"> 171 - Choose which information to show in task views 241 + {t("settings:preferencesPage.displayOptionsDescription")} 172 242 </p> 173 243 </div> 174 244 <Button ··· 178 248 className="flex items-center gap-2" 179 249 > 180 250 <RotateCcw className="h-4 w-4" /> 181 - Reset 251 + {t("common:actions.reset")} 182 252 </Button> 183 253 </div> 184 254 185 255 <div className="space-y-4 border border-border rounded-md p-4 bg-sidebar"> 186 256 <div className="flex items-center justify-between"> 187 257 <div className="space-y-0.5"> 188 - <Label className="text-sm font-medium">Task Numbers</Label> 258 + <Label className="text-sm font-medium"> 259 + {t("settings:preferencesPage.taskNumbers")} 260 + </Label> 189 261 <p className="text-xs text-muted-foreground"> 190 - Show task IDs and numbers 262 + {t("settings:preferencesPage.taskNumbersDescription")} 191 263 </p> 192 264 </div> 193 265 <Switch ··· 200 272 201 273 <div className="flex items-center justify-between"> 202 274 <div className="space-y-0.5"> 203 - <Label className="text-sm font-medium">Assignees</Label> 275 + <Label className="text-sm font-medium"> 276 + {t("settings:preferencesPage.assignees")} 277 + </Label> 204 278 <p className="text-xs text-muted-foreground"> 205 - Show who's assigned to tasks 279 + {t("settings:preferencesPage.assigneesDescription")} 206 280 </p> 207 281 </div> 208 282 <Switch ··· 215 289 216 290 <div className="flex items-center justify-between"> 217 291 <div className="space-y-0.5"> 218 - <Label className="text-sm font-medium">Due Dates</Label> 292 + <Label className="text-sm font-medium"> 293 + {t("settings:preferencesPage.dueDates")} 294 + </Label> 219 295 <p className="text-xs text-muted-foreground"> 220 - Display task deadlines 296 + {t("settings:preferencesPage.dueDatesDescription")} 221 297 </p> 222 298 </div> 223 299 <Switch checked={showDueDates} onCheckedChange={setShowDueDates} /> ··· 227 303 228 304 <div className="flex items-center justify-between"> 229 305 <div className="space-y-0.5"> 230 - <Label className="text-sm font-medium">Labels</Label> 306 + <Label className="text-sm font-medium"> 307 + {t("settings:preferencesPage.labels")} 308 + </Label> 231 309 <p className="text-xs text-muted-foreground"> 232 - Show task labels and tags 310 + {t("settings:preferencesPage.labelsDescription")} 233 311 </p> 234 312 </div> 235 313 <Switch checked={showLabels} onCheckedChange={setShowLabels} /> ··· 239 317 240 318 <div className="flex items-center justify-between"> 241 319 <div className="space-y-0.5"> 242 - <Label className="text-sm font-medium">Priority</Label> 320 + <Label className="text-sm font-medium"> 321 + {t("settings:preferencesPage.priority")} 322 + </Label> 243 323 <p className="text-xs text-muted-foreground"> 244 - Display priority indicators 324 + {t("settings:preferencesPage.priorityDescription")} 245 325 </p> 246 326 </div> 247 327 <Switch checked={showPriority} onCheckedChange={setShowPriority} />
+5 -1
apps/web/src/types/user.ts
··· 1 - export type { User } from "better-auth/types"; 1 + import type { User as BetterAuthUser } from "better-auth/types"; 2 + 3 + export type User = BetterAuthUser & { 4 + locale?: string | null; 5 + };