mirror of
https://github.com/lukevella/rallly.git
synced 2025-06-07 21:21:49 +02:00
✨ Use profile image from oauth provider (#1330)
This commit is contained in:
parent
be216344e2
commit
364bb8b83f
13 changed files with 95 additions and 29 deletions
4
apps/web/declarations/next-auth.d.ts
vendored
4
apps/web/declarations/next-auth.d.ts
vendored
|
@ -11,13 +11,11 @@ declare module "next-auth" {
|
||||||
interface Session {
|
interface Session {
|
||||||
user: {
|
user: {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string | null;
|
|
||||||
email?: string | null;
|
|
||||||
timeZone?: string | null;
|
timeZone?: string | null;
|
||||||
timeFormat?: TimeFormat | null;
|
timeFormat?: TimeFormat | null;
|
||||||
locale?: string | null;
|
locale?: string | null;
|
||||||
weekStart?: number | null;
|
weekStart?: number | null;
|
||||||
};
|
} & DefaultSession["user"];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User extends DefaultUser {
|
interface User extends DefaultUser {
|
||||||
|
|
|
@ -18,9 +18,9 @@ import {
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
|
||||||
|
import { CurrentUserAvatar } from "@/components/current-user-avatar";
|
||||||
import { ProBadge } from "@/components/pro-badge";
|
import { ProBadge } from "@/components/pro-badge";
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
import { CurrentUserAvatar } from "@/components/user";
|
|
||||||
import { IfGuest, useUser } from "@/components/user-provider";
|
import { IfGuest, useUser } from "@/components/user-provider";
|
||||||
import { IfFreeUser } from "@/contexts/plan";
|
import { IfFreeUser } from "@/contexts/plan";
|
||||||
import { IconComponent } from "@/types";
|
import { IconComponent } from "@/types";
|
||||||
|
|
14
apps/web/src/components/current-user-avatar.tsx
Normal file
14
apps/web/src/components/current-user-avatar.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
"use client";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@rallly/ui/avatar";
|
||||||
|
|
||||||
|
import { useUser } from "@/components/user-provider";
|
||||||
|
|
||||||
|
export const CurrentUserAvatar = ({ className }: { className?: string }) => {
|
||||||
|
const { user } = useUser();
|
||||||
|
return (
|
||||||
|
<Avatar className={className}>
|
||||||
|
<AvatarImage src={user.image ?? undefined} />
|
||||||
|
<AvatarFallback>{user.name[0]}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
);
|
||||||
|
};
|
|
@ -9,8 +9,8 @@ import {
|
||||||
import { Input } from "@rallly/ui/input";
|
import { Input } from "@rallly/ui/input";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
import { CurrentUserAvatar } from "@/components/current-user-avatar";
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
import { UserAvatar } from "@/components/user";
|
|
||||||
import { useUser } from "@/components/user-provider";
|
import { useUser } from "@/components/user-provider";
|
||||||
|
|
||||||
export const ProfileSettings = () => {
|
export const ProfileSettings = () => {
|
||||||
|
@ -26,9 +26,7 @@ export const ProfileSettings = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { control, watch, handleSubmit, formState, reset } = form;
|
const { control, handleSubmit, formState, reset } = form;
|
||||||
|
|
||||||
const watchName = watch("name");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-y-4">
|
<div className="grid gap-y-4">
|
||||||
|
@ -41,7 +39,7 @@ export const ProfileSettings = () => {
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-4">
|
||||||
<div>
|
<div>
|
||||||
<UserAvatar name={watchName} size="lg" />
|
<CurrentUserAvatar className="size-14" />
|
||||||
</div>
|
</div>
|
||||||
<FormField
|
<FormField
|
||||||
control={control}
|
control={control}
|
||||||
|
|
|
@ -26,10 +26,10 @@ import {
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
import { CurrentUserAvatar } from "@/components/current-user-avatar";
|
||||||
import { LoginLink } from "@/components/login-link";
|
import { LoginLink } from "@/components/login-link";
|
||||||
import { RegisterLink } from "@/components/register-link";
|
import { RegisterLink } from "@/components/register-link";
|
||||||
import { Trans } from "@/components/trans";
|
import { Trans } from "@/components/trans";
|
||||||
import { CurrentUserAvatar } from "@/components/user";
|
|
||||||
import { IfCloudHosted, IfSelfHosted } from "@/contexts/environment";
|
import { IfCloudHosted, IfSelfHosted } from "@/contexts/environment";
|
||||||
import { Plan, usePlan } from "@/contexts/plan";
|
import { Plan, usePlan } from "@/contexts/plan";
|
||||||
import { isFeedbackEnabled } from "@/utils/constants";
|
import { isFeedbackEnabled } from "@/utils/constants";
|
||||||
|
@ -57,7 +57,7 @@ export const UserDropdown = ({ className }: { className?: string }) => {
|
||||||
className={cn("group min-w-0", className)}
|
className={cn("group min-w-0", className)}
|
||||||
>
|
>
|
||||||
<Button variant="ghost">
|
<Button variant="ghost">
|
||||||
<CurrentUserAvatar size="xs" className="shrink-0 " />
|
<CurrentUserAvatar className="size-6" />
|
||||||
<span className="truncate">{user.name}</span>
|
<span className="truncate">{user.name}</span>
|
||||||
<Icon>
|
<Icon>
|
||||||
<ChevronDownIcon />
|
<ChevronDownIcon />
|
||||||
|
|
|
@ -21,6 +21,7 @@ const userSchema = z.object({
|
||||||
timeZone: z.string().nullish(),
|
timeZone: z.string().nullish(),
|
||||||
timeFormat: z.enum(["hours12", "hours24"]).nullish(),
|
timeFormat: z.enum(["hours12", "hours24"]).nullish(),
|
||||||
weekStart: z.number().min(0).max(6).nullish(),
|
weekStart: z.number().min(0).max(6).nullish(),
|
||||||
|
image: z.string().nullish(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UserContext = React.createContext<{
|
export const UserContext = React.createContext<{
|
||||||
|
@ -76,9 +77,10 @@ export const UserProvider = (props: { children?: React.ReactNode }) => {
|
||||||
id: user.id as string,
|
id: user.id as string,
|
||||||
name: user.name ?? t("guest"),
|
name: user.name ?? t("guest"),
|
||||||
email: user.email || null,
|
email: user.email || null,
|
||||||
isGuest: !user.email,
|
isGuest,
|
||||||
tier,
|
tier,
|
||||||
timeZone: user.timeZone ?? null,
|
timeZone: user.timeZone ?? null,
|
||||||
|
image: user.image ?? null,
|
||||||
},
|
},
|
||||||
refresh: session.update,
|
refresh: session.update,
|
||||||
ownsObject: ({ userId }) => {
|
ownsObject: ({ userId }) => {
|
||||||
|
|
|
@ -3,24 +3,8 @@ import { cn } from "@rallly/ui";
|
||||||
import { Icon } from "@rallly/ui/icon";
|
import { Icon } from "@rallly/ui/icon";
|
||||||
import { User2Icon } from "lucide-react";
|
import { User2Icon } from "lucide-react";
|
||||||
|
|
||||||
import { useUser } from "@/components/user-provider";
|
|
||||||
import { getRandomAvatarColor } from "@/utils/color-hash";
|
import { getRandomAvatarColor } from "@/utils/color-hash";
|
||||||
|
|
||||||
export const CurrentUserAvatar = ({
|
|
||||||
size = "md",
|
|
||||||
className,
|
|
||||||
}: Omit<UserAvatarProps, "name">) => {
|
|
||||||
const { user } = useUser();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<UserAvatar
|
|
||||||
className={className}
|
|
||||||
name={user.isGuest ? undefined : user.name}
|
|
||||||
size={size}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface UserAvatarProps {
|
interface UserAvatarProps {
|
||||||
name?: string;
|
name?: string;
|
||||||
size?: "xs" | "sm" | "md" | "lg";
|
size?: "xs" | "sm" | "md" | "lg";
|
||||||
|
|
|
@ -54,6 +54,7 @@ const providers: Provider[] = [
|
||||||
locale: true,
|
locale: true,
|
||||||
timeFormat: true,
|
timeFormat: true,
|
||||||
timeZone: true,
|
timeZone: true,
|
||||||
|
image: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -265,6 +266,7 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
||||||
timeZone: session.timeZone,
|
timeZone: session.timeZone,
|
||||||
weekStart: session.weekStart,
|
weekStart: session.weekStart,
|
||||||
name: session.name,
|
name: session.name,
|
||||||
|
image: session.image,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -278,6 +280,7 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
||||||
token.timeFormat = user.timeFormat;
|
token.timeFormat = user.timeFormat;
|
||||||
token.timeZone = user.timeZone;
|
token.timeZone = user.timeZone;
|
||||||
token.weekStart = user.weekStart;
|
token.weekStart = user.weekStart;
|
||||||
|
token.picture = user.image;
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
|
@ -288,6 +291,7 @@ const getAuthOptions = (...args: GetServerSessionParams) =>
|
||||||
session.user.timeZone = token.timeZone;
|
session.user.timeZone = token.timeZone;
|
||||||
session.user.locale = token.locale;
|
session.user.locale = token.locale;
|
||||||
session.user.weekStart = token.weekStart;
|
session.user.weekStart = token.weekStart;
|
||||||
|
session.user.image = token.picture;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
|
"@radix-ui/react-avatar": "^1.1.0",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.4",
|
"@radix-ui/react-dialog": "^1.0.4",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
"@radix-ui/react-dropdown-menu": "^2.0.4",
|
||||||
|
|
50
packages/ui/src/avatar.tsx
Normal file
50
packages/ui/src/avatar.tsx
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as AvatarPrimitive from "@radix-ui/react-avatar";
|
||||||
|
|
||||||
|
import { cn } from "@rallly/ui";
|
||||||
|
|
||||||
|
const Avatar = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex size-10 shrink-0 overflow-hidden rounded-full",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
Avatar.displayName = AvatarPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
const AvatarImage = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Image
|
||||||
|
ref={ref}
|
||||||
|
className={cn("aspect-square h-full w-full", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
|
||||||
|
|
||||||
|
const AvatarFallback = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"bg-muted flex h-full w-full items-center justify-center rounded-full",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
|
||||||
|
|
||||||
|
export { Avatar, AvatarImage, AvatarFallback };
|
|
@ -7,7 +7,7 @@ import * as React from "react";
|
||||||
|
|
||||||
import { Dialog, DialogContent } from "./dialog";
|
import { Dialog, DialogContent } from "./dialog";
|
||||||
import { cn } from "./lib/utils";
|
import { cn } from "./lib/utils";
|
||||||
import { Icon } from "@rallly/ui/icon";
|
import { Icon } from "./icon";
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
const Command = React.forwardRef<
|
||||||
React.ElementRef<typeof CommandPrimitive>,
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
"extends": "@rallly/tsconfig/next.json",
|
"extends": "@rallly/tsconfig/next.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/utils": ["src/lib/utils.ts"],
|
||||||
|
"@/components/*": ["src/*"],
|
||||||
|
"@/ui/*": ["src/*"],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["node_modules"],
|
"exclude": ["node_modules"],
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -2872,6 +2872,16 @@
|
||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-primitive" "1.0.3"
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-avatar@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz#457c81334c93f4608df15f081e7baa286558d6a2"
|
||||||
|
integrity sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-context" "1.1.0"
|
||||||
|
"@radix-ui/react-primitive" "2.0.0"
|
||||||
|
"@radix-ui/react-use-callback-ref" "1.1.0"
|
||||||
|
"@radix-ui/react-use-layout-effect" "1.1.0"
|
||||||
|
|
||||||
"@radix-ui/react-checkbox@^1.0.4":
|
"@radix-ui/react-checkbox@^1.0.4":
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue