Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
24 changes: 21 additions & 3 deletions src/app/auth/login/google/callback/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { decodeIdToken } from "arctic";
import { and, eq } from "drizzle-orm";
import { cookies } from "next/headers";
import { db } from "@/db";
import { oauthAccounts, users } from "@/db/schema";
import { members, oauthAccounts } from "@/db/schema";
import { setSessionTokenCookie } from "@/lib/auth/cookies";
import { oauth } from "@/lib/auth/oauth";
import { createSession, generateSessionToken } from "@/lib/auth/session";
import { createGoogleUser } from "@/lib/auth/user";
import { createGoogleUser, generateUsername } from "@/lib/auth/user";
import { convertTimeToUTC } from "@/lib/availability/utils";
import { availabilityPathWithOpenInvite } from "@/lib/meeting-open-invite";
import { createMeetingFromData } from "@/server/actions/meeting/create/action";
Expand Down Expand Up @@ -61,6 +61,7 @@ export async function GET(request: Request): Promise<Response> {
sub: string;
name: string;
email: string;
picture?: string;
};

const oidcAccessToken = tokens.accessToken();
Expand Down Expand Up @@ -93,6 +94,7 @@ export async function GET(request: Request): Promise<Response> {
const oauthUserId = claims.sub;
const username = claims.name;
const email = claims.email;
const picture = claims.picture ?? null;

const existingUser = await db.query.users.findFirst({
where: (users) => eq(users.email, email),
Expand Down Expand Up @@ -135,8 +137,24 @@ export async function GET(request: Request): Promise<Response> {
return new Response(null, { status: 500 });
}
memberId = userRecord.memberId;

const member = await db.query.members.findFirst({
where: eq(members.id, memberId),
columns: { googleName: true, username: true },
});
const backfill: {
googleName?: string;
username?: string;
profilePicture?: string | null;
} = {};
if (!member?.googleName) backfill.googleName = username;
if (!member?.username) backfill.username = await generateUsername(username);
if (picture !== null) backfill.profilePicture = picture;
if (Object.keys(backfill).length > 0) {
await db.update(members).set(backfill).where(eq(members.id, memberId));
}
} else {
const user = await createGoogleUser(oauthUserId, email, username, null);
const user = await createGoogleUser(oauthUserId, email, username, picture);

const sessionToken = generateSessionToken();
const session = await createSession(sessionToken, user.id, {
Expand Down
21 changes: 20 additions & 1 deletion src/app/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { notFound, redirect } from "next/navigation";
import { ProfileSettings } from "@/components/profile/profile-settings";
import { getCurrentSession } from "@/lib/auth";
import { ProfileContent } from "./profile-content";

Expand All @@ -12,5 +15,21 @@ export default async function ProfilePage() {
notFound();
}

return <ProfileContent />;
return (
<Box sx={{ px: 8, py: 8 }}>
<Typography
variant="h4"
sx={{ fontWeight: 600, fontFamily: "Figtree, sans-serif" }}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: figtree font is already applied globally

>
Settings
</Typography>

<Box sx={{ mt: 8 }}>
<ProfileSettings user={session.user} />
</Box>
<Box sx={{ mt: 8, display: { md: "none" } }}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perfer tailwind className styles over sx

<ProfileContent />
</Box>
</Box>
);
}
16 changes: 9 additions & 7 deletions src/app/profile/profile-content.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
"use client";

import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import { useThemeMode } from "@/components/theme/theme-provider";

export function ProfileContent() {
const { mode, setMode } = useThemeMode();

return (
<div className="px-8 py-8">
<h1 className="font-figtree font-medium text-3xl">Profile</h1>
<div className="max-w-sm">
<p className="mb-2 font-medium">Preferred Display Mode</p>

<Box>
<Typography variant="body1" sx={{ fontWeight: 500, mb: 1 }}>
Preferred Display Mode
</Typography>
<Box sx={{ maxWidth: "24rem" }}>
<FormControl fullWidth>
<InputLabel id="theme-select-label">Mode</InputLabel>
<Select
Expand All @@ -27,7 +29,7 @@ export function ProfileContent() {
<MenuItem value="system">System</MenuItem>
</Select>
</FormControl>
</div>
</div>
</Box>
</Box>
);
}
10 changes: 7 additions & 3 deletions src/components/availability/availability.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -413,9 +413,9 @@ export function Availability({
];

const allMembers = new Map(
allAvailabilities.map(({ memberId, displayName }) => [
allAvailabilities.map(({ memberId, displayName, profilePicture }) => [
memberId,
{ memberId, displayName },
{ memberId, displayName, profilePicture },
]),
);

Expand All @@ -434,7 +434,11 @@ export function Availability({
() =>
allAvailabilities
.filter((a) => a.meetingAvailabilities.length === 0)
.map(({ memberId, displayName }) => ({ memberId, displayName })),
.map(({ memberId, displayName, profilePicture }) => ({
memberId,
displayName,
profilePicture,
})),
[allAvailabilities],
);

Expand Down
17 changes: 15 additions & 2 deletions src/components/availability/group-responses.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, Chip, Switch, Typography } from "@mui/material/";
import { Avatar, Button, Chip, Switch, Typography } from "@mui/material/";
import { XIcon } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useShallow } from "zustand/shallow";
Expand Down Expand Up @@ -247,7 +247,13 @@ export function GroupResponses({
<Chip
key={member.memberId}
clickable
//put pfp here once user settings merged. icon={}
icon={
<Avatar
alt={member.displayName}
src={member.profilePicture ?? undefined}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure to add referrerPolicy: "no-referrer" to prevent them from being blocked by Google's CDN

sx={{ width: 24, height: 24, fontSize: 12 }}
/>
}
label={member.displayName}
color={
selectedMembers.includes(member.memberId)
Expand Down Expand Up @@ -291,6 +297,13 @@ export function GroupResponses({
{pendingMembers.map((member) => (
<Chip
key={member.memberId}
icon={
<Avatar
alt={member.displayName}
src={member.profilePicture ?? undefined}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as before: make sure to add referrerPolicy: "no-referrer" to prevent them from being blocked by Google's CDN

sx={{ height: 24, width: 24 }}
/>
}
label={member.displayName}
variant="outlined"
/>
Expand Down
30 changes: 24 additions & 6 deletions src/components/availability/invite-members-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useSnackbar } from "@/components/ui/snackbar-provider";
interface SelectedMember {
id: string;
email: string;
profilePicture: string | null;
}

interface InviteMembersDialogProps {
Expand All @@ -40,7 +41,7 @@ export function InviteMembersDialog({
const [members, setMembers] = useState<SelectedMember[]>([]);
const [memberQuery, setMemberQuery] = useState("");
const [searchResults, setSearchResults] = useState<
{ id: string; email: string }[]
{ id: string; email: string; profilePicture: string | null }[]
>([]);
const [meetingLink, setMeetingLink] = useState("");
const [copied, setCopied] = useState(false);
Expand Down Expand Up @@ -101,9 +102,16 @@ export function InviteMembersDialog({
);

const addMember = useCallback(
(user: { id: string; email: string }) => {
(user: { id: string; email: string; profilePicture: string | null }) => {
if (!members.some((m) => m.id === user.id)) {
setMembers((prev) => [...prev, { id: user.id, email: user.email }]);
setMembers((prev) => [
...prev,
{
id: user.id,
email: user.email,
profilePicture: user.profilePicture,
},
]);
}
setMemberQuery("");
setSearchResults([]);
Expand Down Expand Up @@ -184,9 +192,12 @@ export function InviteMembersDialog({
renderOption={({ key, ...optionProps }, option) => (
<li key={key ?? option.id} {...optionProps}>
<div className="flex items-center gap-3">
<div className="flex size-8 items-center justify-center rounded-full bg-blue-100 font-medium text-blue-700 text-xs">
<Avatar
src={option.profilePicture ?? undefined}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
>
{getInitials(option.email)}
</div>
</Avatar>
<span className="text-sm">{option.email}</span>
</div>
</li>
Expand All @@ -198,7 +209,14 @@ export function InviteMembersDialog({
{members.map((member) => (
<Chip
key={member.id}
avatar={<Avatar>{getInitials(member.email)}</Avatar>}
avatar={
<Avatar
src={member.profilePicture ?? undefined}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
>
{getInitials(member.email)}
</Avatar>
}
label={member.email.split("@")[0]}
onDelete={() => removeMember(member.id)}
variant="filled"
Expand Down
30 changes: 24 additions & 6 deletions src/components/groups/create-group-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
interface SelectedMember {
id: string;
email: string;
profilePicture: string | null;
}

interface CreateGroupDialogProps {
Expand Down Expand Up @@ -86,7 +87,7 @@ export function CreateGroupDialog({
const [members, setMembers] = useState<SelectedMember[]>([]);
const [memberQuery, setMemberQuery] = useState("");
const [searchResults, setSearchResults] = useState<
{ id: string; email: string }[]
{ id: string; email: string; profilePicture: string | null }[]
>([]);
const [inviteLink, setInviteLink] = useState("");
const [copied, setCopied] = useState(false);
Expand Down Expand Up @@ -162,9 +163,16 @@ export function CreateGroupDialog({
);

const addMember = useCallback(
(user: { id: string; email: string }) => {
(user: { id: string; email: string; profilePicture: string | null }) => {
if (!members.some((m) => m.id === user.id)) {
setMembers((prev) => [...prev, { id: user.id, email: user.email }]);
setMembers((prev) => [
...prev,
{
id: user.id,
email: user.email,
profilePicture: user.profilePicture,
},
]);
}
setMemberQuery("");
setSearchResults([]);
Expand Down Expand Up @@ -296,9 +304,12 @@ export function CreateGroupDialog({
renderOption={({ key, ...optionProps }, option) => (
<li key={key ?? option.id} {...optionProps}>
<div className="flex items-center gap-3">
<div className="flex size-8 items-center justify-center rounded-full bg-blue-100 font-medium text-blue-700 text-xs">
<Avatar
src={option.profilePicture ?? undefined}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
>
{getInitials(option.email)}
</div>
</Avatar>
<span className="text-sm">{option.email}</span>
</div>
</li>
Expand All @@ -310,7 +321,14 @@ export function CreateGroupDialog({
{members.map((member) => (
<Chip
key={member.id}
avatar={<Avatar>{getInitials(member.email)}</Avatar>}
avatar={
<Avatar
src={member.profilePicture ?? undefined}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
>
{getInitials(member.email)}
</Avatar>
}
label={member.email.split("@")[0]}
onDelete={() => removeMember(member.id)}
variant="filled"
Expand Down
13 changes: 11 additions & 2 deletions src/components/nav/mui-top-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,12 @@ function Notifications({
>
<Avatar
alt={notif.title || "Group icon"}
src={notif.groupIcon || "/icssc-logo.svg"}
src={
notif.createdByAvatar ||
notif.groupIcon ||
"/icssc-logo.svg"
}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
/>
<Box sx={{ p: 1 }}>
<Typography variant="body1">{notif.title}</Typography>
Expand Down Expand Up @@ -329,7 +334,11 @@ function NavUser({ user }: { user: UserProfile | null }) {
gap: 1,
}}
>
<Avatar sx={{ width: 32, height: 32 }}>
<Avatar
src={user.profilePicture ?? undefined}
slotProps={{ img: { referrerPolicy: "no-referrer" } }}
sx={{ width: 32, height: 32 }}
>
<Person fontSize="small" />
</Avatar>
<Typography
Expand Down
Loading
Loading