Skip to content

Commit

Permalink
Update settings page
Browse files Browse the repository at this point in the history
  • Loading branch information
arjunkomath committed Jan 11, 2025
1 parent 6a08860 commit e3f187b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 16 deletions.
12 changes: 12 additions & 0 deletions app/(dashboard)/[tenant]/settings/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import { updateUser } from "@/lib/ops/auth";
import { getOwner } from "@/lib/utils/useOwner";
import { revalidatePath } from "next/cache";
import { cookies } from "next/headers";

export async function saveUserTimezone(timezone: string) {
Expand All @@ -15,3 +16,14 @@ export async function saveUserTimezone(timezone: string) {
const { userId } = await getOwner();
await updateUser(userId, { customData: { timezone } });
}

export async function updateUserData(payload: FormData) {
const { userId, orgSlug } = await getOwner();
const key = payload.get("key") as string;
const value = payload.get(key) as string;
await updateUser(userId, {
[key]: value,
});
revalidatePath(`/${orgSlug}/settings`);
return { success: true };
}
68 changes: 54 additions & 14 deletions app/(dashboard)/[tenant]/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { logtoConfig } from "@/app/logto";
import { EditableValue } from "@/components/core/editable-text";
import PageSection from "@/components/core/section";
import PageTitle from "@/components/layout/page-title";
import { blob } from "@/drizzle/schema";
import { bytesToMegabytes } from "@/lib/blobStore";
import { getUser } from "@/lib/ops/auth";
import { database } from "@/lib/utils/useDatabase";
import { getLogtoContext } from "@logto/next/server-actions";
import { sql } from "drizzle-orm";
import { HardDrive, User2 } from "lucide-react";
import { notFound } from "next/navigation";
import { updateUserData } from "./actions";

export default async function Settings() {
const { claims, userInfo } = await getLogtoContext(logtoConfig, {
const { claims } = await getLogtoContext(logtoConfig, {
fetchUserInfo: true,
});

Expand All @@ -20,15 +23,16 @@ export default async function Settings() {

const db = await database();

const [storage] = [
const [storage, user] = await Promise.all([
db
.select({
count: sql<number>`count(*)`,
usage: sql<number>`sum(${blob.contentSize})`,
})
.from(blob)
.get(),
];
getUser(claims.sub),
]);

return (
<>
Expand Down Expand Up @@ -56,27 +60,63 @@ export default async function Settings() {
</dl>
</PageSection>

<PageSection className="p-4">
<h2 className="flex items-center text-xl font-semibold leading-7 text-gray-900 dark:text-gray-200">
<User2 className="mr-2 inline-block h-6 w-6" />
Profile ({userInfo?.username})
</h2>
{user ? (
<PageSection className="p-4">
<h2 className="flex items-center text-xl font-semibold leading-7 text-gray-900 dark:text-gray-200">
<User2 className="mr-2 inline-block h-6 w-6" />
Profile ({user.username})
</h2>

<dl className="mt-6 space-y-6 divide-y divide-gray-100 text-sm leading-6 dark:divide-gray-800">
<div className="pt-6 sm:flex">
<dt className="font-semibold text-gray-900 dark:text-gray-200 sm:w-64 sm:flex-none sm:pr-6">
Name
</dt>
<dd className="mt-1 flex justify-between gap-x-6 sm:mt-0 sm:flex-auto">
<div className="text-gray-900 dark:text-gray-200">
<EditableValue
id={claims.sub}
name="name"
type="text"
value={user.name ?? "-"}
action={updateUserData}
/>
</div>
</dd>
</div>

<dl className="mt-6 space-y-6 divide-y divide-gray-100 text-sm leading-6 dark:divide-gray-800">
{userInfo?.email ? (
<div className="pt-6 sm:flex">
<dt className="font-semibold text-gray-900 dark:text-gray-200 sm:w-64 sm:flex-none sm:pr-6">
Email address
</dt>
<dd className="mt-1 flex justify-between gap-x-6 sm:mt-0 sm:flex-auto">
<div className="text-gray-900 dark:text-gray-200">
{userInfo.email}
<EditableValue
id={claims.sub}
name="primaryEmail"
type="text"
value={user.primaryEmail ?? "-"}
action={updateUserData}
/>
</div>
</dd>
</div>
) : null}
</dl>
</PageSection>

{user.customData?.timezone ? (
<div className="pt-6 sm:flex">
<dt className="font-semibold text-gray-900 dark:text-gray-200 sm:w-64 sm:flex-none sm:pr-6">
Timezone
</dt>
<dd className="mt-1 flex justify-between gap-x-6 sm:mt-0 sm:flex-auto">
<div className="text-gray-900 dark:text-gray-200">
{user.customData?.timezone}
</div>
</dd>
</div>
) : null}
</dl>
</PageSection>
) : null}
</>
);
}
69 changes: 69 additions & 0 deletions components/core/editable-text.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"use client";

import { useState } from "react";
import { notifyError, notifySuccess } from "../core/toast";
import { ActionButton } from "../form/button";
import { Button } from "../ui/button";
import { Input } from "../ui/input";

export type ResultWithOptionalError = { error?: string; success: boolean };

export function EditableValue({
id,
name,
value,
action,
type,
}: {
id: string | number;
name: string;
value: string | number;
type: "text" | "number";
action: (data: FormData) => Promise<ResultWithOptionalError>;
}) {
const [isEditing, setIsEditing] = useState(false);
const [localValue, setLocalValue] = useState(value);

return (
<form
action={async (formData: FormData) => {
try {
const result = await action(formData);
if (result?.error) {
notifyError(result.error);
} else {
notifySuccess("Updated successfully");
}
} finally {
setIsEditing(false);
}
}}
>
<input type="hidden" name="id" value={id} />
<input type="hidden" name="key" value={name} />
{isEditing ? (
<div className="flex space-x-2">
<Input
name={name}
type={type}
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
className="w-auto max-w-[160px]"
/>
<ActionButton label="Save" />
</div>
) : (
<div className="flex items-center">
<p>{value}</p>
<Button
type="button"
variant="link"
onClick={() => setIsEditing(true)}
>
Edit
</Button>
</div>
)}
</form>
);
}
2 changes: 1 addition & 1 deletion components/project/events/events-calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default function EventsCalendar({
return (
<div className="flex w-full flex-col md:flex-row md:space-x-2">
<Calendar
className="block mx-auto pb-10 md:pb-3 md:mx-0"
className="block mx-auto md:mx-0"
mode="single"
selected={new Date(selectedDate)}
onDayClick={(date) => {
Expand Down
18 changes: 17 additions & 1 deletion lib/ops/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ const tenantId = "default";
* https://openapi.logto.io
*/

export interface User {
id: string;
username: string;
primaryEmail: string | null;
name: string | null;
avatar: string | null;
customData: {
timezone: string;
};
lastSignInAt: number;
createdAt: number;
updatedAt: number;
isSuspended: boolean;
hasPassword: boolean;
}

export interface Organization {
tenantId: string;
id: string;
Expand Down Expand Up @@ -47,7 +63,7 @@ export const fetchAccessToken = async () => {
});
};

export const getUser = async (userId: string) => {
export const getUser = async (userId: string): Promise<User> => {
const { access_token } = await fetchAccessToken().then((res) => res.json());
if (!access_token) {
throw new Error("Access token not found");
Expand Down

0 comments on commit e3f187b

Please sign in to comment.