fixed device registration and onboarding flow
This commit is contained in:
parent
c4743896a4
commit
953169d8ad
9 changed files with 128 additions and 468 deletions
|
|
@ -4,9 +4,8 @@ import { encodedRedirect } from "@/utils/utils";
|
|||
import { createClient } from "@/utils/supabase/server";
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getMacAddressFromDeviceCode, isValidMacAddress } from "@/lib/utils";
|
||||
import { addUserToDevice, dbCheckUserCode } from "@/db/devices";
|
||||
import { getSimpleUserById, updateUser } from "@/db/users";
|
||||
import { getSimpleUserById } from "@/db/users";
|
||||
|
||||
export async function deleteUserApiKey(userId: string) {
|
||||
const supabase = createClient();
|
||||
|
|
@ -174,30 +173,3 @@ export const isPremiumUser = async (userId: string) => {
|
|||
const dbUser = await getSimpleUserById(supabase, userId);
|
||||
return dbUser?.is_premium;
|
||||
};
|
||||
|
||||
export async function registerDevice(userId: string, deviceCode: string) {
|
||||
// check if deviceCode is valid mac address
|
||||
if (!isValidMacAddress(deviceCode)) {
|
||||
return { error: "Invalid device code" };
|
||||
}
|
||||
|
||||
const supabase = createClient();
|
||||
const { data, error } = await supabase
|
||||
.from("devices")
|
||||
.insert({
|
||||
user_id: userId,
|
||||
user_code: deviceCode, // this is the device code that the user will use to register their device (friendly code preferred)
|
||||
mac_address: deviceCode,
|
||||
}).select();
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return { error: "Error registering device" };
|
||||
}
|
||||
|
||||
if (data && data.length > 0) {
|
||||
await updateUser(supabase, { device_id: data[0].device_id }, userId);
|
||||
}
|
||||
|
||||
return { error: null };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,135 +1,57 @@
|
|||
|
||||
"use client";
|
||||
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import UserType from "./UserType";
|
||||
import React from "react";
|
||||
import GeneralUserForm from "../Settings/UserForm";
|
||||
import DoctorForm from "../Settings/DoctorForm";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { checkDoctorAction } from "@/app/actions";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
|
||||
type TUserType = "doctor" | "user" | "business";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
import { updateUser } from "@/db/users";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
const Steps: React.FC<{
|
||||
selectedUser: IUser;
|
||||
}> = ({ selectedUser }) => {
|
||||
selectedUser?: IUser;
|
||||
userId: string;
|
||||
}> = ({ selectedUser, userId }) => {
|
||||
const supabase = createClient();
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
const [progress, setProgress] = React.useState(40);
|
||||
const [step, setStep] = React.useState(0);
|
||||
const [selectedType, setSelectedType] = useState<TUserType | null>(null);
|
||||
const [doctorAuthCode, setDoctorAuthCode] = useState<string>("");
|
||||
|
||||
const onSelectType = (type: TUserType) => {
|
||||
setSelectedType(type);
|
||||
setStep(1);
|
||||
setProgress(progress + 30);
|
||||
};
|
||||
|
||||
const onClickBack = () => {
|
||||
setStep(step - 1);
|
||||
setProgress(progress - 30);
|
||||
};
|
||||
const [progress, setProgress] = React.useState(50);
|
||||
const [step, setStep] = React.useState(1);
|
||||
|
||||
const onClickFormCallback = async () => {
|
||||
if (selectedType === "doctor") {
|
||||
const res = await checkDoctorAction(doctorAuthCode);
|
||||
if (!res) {
|
||||
toast({
|
||||
description:
|
||||
"Your sign-up code did not match our records. Try again or reach out to us for help.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setStep(step + 1);
|
||||
setProgress(progress + 30);
|
||||
setProgress(progress + 50);
|
||||
router.push("/home");
|
||||
};
|
||||
|
||||
const CurrentForm = () => {
|
||||
if (step === 0) {
|
||||
return (
|
||||
<UserType
|
||||
selectedUser={selectedUser}
|
||||
onSelectType={onSelectType}
|
||||
selectedType={selectedType}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
if (selectedType === "doctor") {
|
||||
return (
|
||||
<DoctorForm
|
||||
selectedUser={selectedUser}
|
||||
heading={<Navigation />}
|
||||
onClickCallback={onClickFormCallback}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<GeneralUserForm
|
||||
selectedUser={selectedUser}
|
||||
heading={<Navigation />}
|
||||
onClickCallback={onClickFormCallback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const Navigation = () => {
|
||||
if (step === 1) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="mt-4 text-center flex flex-row items-center justify-between gap-4">
|
||||
{step > 0 && (
|
||||
<Button
|
||||
onClick={onClickBack}
|
||||
variant="link"
|
||||
size="sm"
|
||||
type="button"
|
||||
className="mr-4 pl-0 flex flex-row items-center gap-2"
|
||||
>
|
||||
<ArrowLeft size={16} /> Back
|
||||
</Button>
|
||||
)}
|
||||
{step > 0 && (
|
||||
<Button
|
||||
disabled={!selectedType}
|
||||
className="flex flex-row items-center gap-2"
|
||||
size="sm"
|
||||
type="submit"
|
||||
>
|
||||
Continue <ArrowRight size={16} />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{selectedType === "doctor" && (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="flex flex-row gap-4 items-center">
|
||||
{"Your unique sign-up code"}
|
||||
</Label>
|
||||
<Input
|
||||
type="text"
|
||||
autoFocus
|
||||
required
|
||||
value={doctorAuthCode}
|
||||
onChange={(e) => setDoctorAuthCode(e.target.value)}
|
||||
placeholder="Sign-up code"
|
||||
className="max-w-screen-sm h-10 bg-white"
|
||||
autoComplete="on"
|
||||
style={{
|
||||
fontSize: 16,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<GeneralUserForm
|
||||
selectedUser={selectedUser}
|
||||
userId={userId}
|
||||
onClickCallback={onClickFormCallback}
|
||||
onSave={
|
||||
async (values, userType) => {
|
||||
await updateUser(
|
||||
supabase,
|
||||
{
|
||||
supervisee_age: values.supervisee_age,
|
||||
supervisee_name: values.supervisee_name,
|
||||
supervisee_persona: values.supervisee_persona,
|
||||
user_info: {
|
||||
user_type: userType,
|
||||
user_metadata: values,
|
||||
},
|
||||
},
|
||||
userId);
|
||||
}}
|
||||
disabled={false}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return <Loader2 className="w-4 h-4 animate-spin" />;
|
||||
}
|
||||
};
|
||||
|
||||
let heading = "Let's get your Elato device & account set up";
|
||||
|
|
@ -137,11 +59,7 @@ const Steps: React.FC<{
|
|||
"We want to make sure that your Elato is set up to provide you the best experience possible.";
|
||||
|
||||
if (step === 1) {
|
||||
if (selectedType === "doctor") {
|
||||
heading = "Hello Doctor!";
|
||||
subHeading =
|
||||
"With the following details we will be able to personalize your and your patients' Elato experience.";
|
||||
} else {
|
||||
{
|
||||
heading = "Hello there!";
|
||||
subHeading =
|
||||
"With the following details we will be able to personalize your Elato experience.";
|
||||
|
|
@ -158,4 +76,4 @@ const Steps: React.FC<{
|
|||
);
|
||||
};
|
||||
|
||||
export default Steps;
|
||||
export default Steps;
|
||||
|
|
@ -24,12 +24,6 @@ const UserTypes: IUserType[] = [
|
|||
title: "You are looking to use Elato for personal use",
|
||||
icon: <User />,
|
||||
},
|
||||
{
|
||||
type: "doctor",
|
||||
name: "Doctor",
|
||||
title: "You are a licensed doctor or physician",
|
||||
icon: <Hospital />,
|
||||
},
|
||||
{
|
||||
type: "business",
|
||||
name: "Business",
|
||||
|
|
@ -40,10 +34,9 @@ const UserTypes: IUserType[] = [
|
|||
];
|
||||
|
||||
const UserType: React.FC<{
|
||||
selectedUser: IUser;
|
||||
selectedType: TUserType | null;
|
||||
onSelectType: (type: TUserType) => void;
|
||||
}> = ({ selectedType, onSelectType, selectedUser }) => {
|
||||
}> = ({ selectedType, onSelectType }) => {
|
||||
const onPickType = async (userType: IUserType) => {
|
||||
if (!userType.disabled) {
|
||||
onSelectType(userType.type);
|
||||
|
|
@ -55,7 +48,7 @@ const UserType: React.FC<{
|
|||
<h1 className="text-xl font-medium my-4 inline">
|
||||
You are a <ArrowRight className="inline-block" />{" "}
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 mt-2 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 mt-2 gap-6">
|
||||
{UserTypes.map((userType) => (
|
||||
<Card
|
||||
key={userType.type}
|
||||
|
|
@ -98,6 +91,4 @@ const UserType: React.FC<{
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserType;
|
||||
};
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { registerDevice, signOutAction } from "@/app/actions";
|
||||
|
||||
import { connectUserToDevice, signOutAction } from "@/app/actions";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { LogOut } from "lucide-react";
|
||||
import DoctorForm from "./DoctorForm";
|
||||
|
||||
import GeneralUserForm from "./UserForm";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { updateUser } from "@/db/users";
|
||||
|
|
@ -12,12 +13,6 @@ import { createClient } from "@/utils/supabase/client";
|
|||
import React, { useCallback } from "react";
|
||||
import { doesUserHaveADevice, updateDevice } from "@/db/devices";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
TooltipProvider,
|
||||
} from "@/components/ui/tooltip";
|
||||
|
||||
interface AppSettingsProps {
|
||||
selectedUser: IUser;
|
||||
|
|
@ -35,7 +30,7 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
const userFormRef = React.useRef<{ submitForm: () => void } | null>(null);
|
||||
const [deviceCode, setDeviceCode] = React.useState("");
|
||||
const [error, setError] = React.useState("");
|
||||
|
||||
|
||||
const handleSave = () => {
|
||||
if (selectedUser.user_info.user_type === "doctor") {
|
||||
doctorFormRef.current?.submitForm();
|
||||
|
|
@ -50,6 +45,11 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
);
|
||||
}, [selectedUser.user_id, supabase]);
|
||||
|
||||
React.useEffect(() => {
|
||||
checkIfUserHasDevice();
|
||||
}, [checkIfUserHasDevice]);
|
||||
|
||||
|
||||
const [volume, setVolume] = React.useState([
|
||||
selectedUser.device?.volume ?? 50,
|
||||
]);
|
||||
|
|
@ -69,7 +69,8 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
debouncedUpdateVolume();
|
||||
};
|
||||
|
||||
const onSave = async (values: any, userType: "doctor" | "user") => {
|
||||
const onSave = async (values: any, userType: "doctor" | "user", userId: string) => {
|
||||
|
||||
await updateUser(
|
||||
supabase,
|
||||
{
|
||||
|
|
@ -81,29 +82,22 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
user_metadata: values,
|
||||
},
|
||||
},
|
||||
selectedUser!.user_id);
|
||||
toast({
|
||||
description: "Your prefereces have been saved!",
|
||||
});
|
||||
}
|
||||
userId);
|
||||
toast({
|
||||
description: "Your prefereces have been saved!",
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{selectedUser.user_info.user_type === "doctor" ? (
|
||||
<DoctorForm
|
||||
<GeneralUserForm
|
||||
selectedUser={selectedUser}
|
||||
userId={selectedUser.user_id}
|
||||
heading={heading}
|
||||
onSave={onSave}
|
||||
onClickCallback={() => handleSave()}
|
||||
/>
|
||||
) : (
|
||||
<GeneralUserForm
|
||||
selectedUser={selectedUser}
|
||||
heading={heading}
|
||||
onSave={onSave}
|
||||
onClickCallback={() => handleSave()}
|
||||
/>
|
||||
)}
|
||||
|
||||
<section className="space-y-4 max-w-screen-sm mt-12">
|
||||
<h2 className="text-lg font-semibold border-b border-gray-200 pb-2">
|
||||
Device settings
|
||||
|
|
@ -112,18 +106,8 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row items-center gap-2">
|
||||
<Label className="text-sm font-medium text-gray-700">
|
||||
Register your device
|
||||
</Label>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger className="inline-flex items-center justify-center h-4 w-4 rounded-full bg-gray-200 text-gray-600 text-xs hover:bg-gray-300">
|
||||
?
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>For simplicity, you can register your ESP32 MAC address here. <br /> Ideally you want this to be a friendly code for your device.</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
Register your device
|
||||
</Label>
|
||||
<div
|
||||
className={`rounded-full flex-shrink-0 h-2 w-2 ${
|
||||
isConnected ? 'bg-green-500' : 'bg-amber-500'
|
||||
|
|
@ -131,22 +115,23 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
/>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div className="flex flex-row items-center gap-2 mt-2">
|
||||
<Input
|
||||
value={deviceCode}
|
||||
disabled={isConnected}
|
||||
onChange={(e) => setDeviceCode(e.target.value)}
|
||||
placeholder={isConnected ? "**********" : "Enter your ESP32-S3 MAC address"}
|
||||
placeholder={isConnected ? "**********" : "Enter your device code"}
|
||||
maxLength={100}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={isConnected}
|
||||
onClick={async () => {
|
||||
const result = await registerDevice(selectedUser.user_id, deviceCode);
|
||||
if (result.error) {
|
||||
setError(result.error);
|
||||
const result = await connectUserToDevice(selectedUser.user_id, deviceCode);
|
||||
if (!result) {
|
||||
setError("Error registering device");
|
||||
}
|
||||
checkIfUserHasDevice();
|
||||
}}
|
||||
|
|
@ -157,10 +142,10 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
<p className="text-xs text-gray-400">
|
||||
{isConnected ? <span className="font-medium text-gray-800">Registered!</span> :
|
||||
error ? <span className="text-red-500">{error}.</span> :
|
||||
"Add your ESP32-S3 MAC address (e.g. 12:34:56:78:9A:BC) to your account to register it."
|
||||
"Enter your device code to register it."
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<Label className="text-sm font-medium text-gray-700">
|
||||
Logged in as
|
||||
|
|
@ -212,4 +197,4 @@ const AppSettings: React.FC<AppSettingsProps> = ({
|
|||
);
|
||||
};
|
||||
|
||||
export default AppSettings;
|
||||
export default AppSettings;
|
||||
|
|
@ -1,218 +0,0 @@
|
|||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import React, { forwardRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface DoctorFormProps {
|
||||
selectedUser: IUser;
|
||||
heading: React.ReactNode;
|
||||
onSave?: (values: any, userType: "doctor" | "user") => void;
|
||||
onClickCallback: () => void;
|
||||
}
|
||||
|
||||
export const doctorSettingsSchema = z.object({
|
||||
doctor_name: z.string().min(1).max(50),
|
||||
specialization: z.string().min(1).max(500),
|
||||
hospital_name: z.string().min(1).max(200),
|
||||
favorite_phrases: z.string().min(1).max(200),
|
||||
hospital_layout: z.string().min(1).max(500),
|
||||
});
|
||||
|
||||
export type DoctorSettingsInput = z.infer<typeof doctorSettingsSchema>;
|
||||
|
||||
const DoctorForm = ({ selectedUser, heading, onSave, onClickCallback }: DoctorFormProps) => {
|
||||
const userMetadata = selectedUser.user_info
|
||||
.user_metadata as IDoctorMetadata;
|
||||
|
||||
const form = useForm<DoctorSettingsInput>({
|
||||
defaultValues: {
|
||||
doctor_name:
|
||||
userMetadata?.doctor_name ?? selectedUser.supervisee_name,
|
||||
specialization: userMetadata?.specialization ?? "",
|
||||
hospital_name: userMetadata?.hospital_name ?? "",
|
||||
favorite_phrases: userMetadata?.favorite_phrases ?? "",
|
||||
hospital_layout: userMetadata?.hospital_layout ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof doctorSettingsSchema>) {
|
||||
onSave && onSave(values, "doctor");
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
onSave && onSave(form.getValues(), "doctor");
|
||||
onClickCallback();
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="flex flex-col gap-8 mb-4 max-w-screen-sm"
|
||||
>
|
||||
{heading}
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-lg font-semibold border-b border-gray-200 pb-2">
|
||||
Basic Info
|
||||
</h2>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="doctor_name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full rounded-md">
|
||||
<FormLabel className="text-sm font-medium text-gray-700">
|
||||
{"Your Name"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
// autoFocus
|
||||
required
|
||||
placeholder="e.g. Dr. John Doe"
|
||||
{...field}
|
||||
className="mt-1"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</section>
|
||||
<section className="space-y-4 flex flex-col gap-2">
|
||||
<h2 className="text-lg font-semibold border-b border-gray-200 pb-2">
|
||||
Hospital Details
|
||||
</h2>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="hospital_name"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full rounded-md">
|
||||
<FormLabel className="text-sm font-medium text-gray-700">
|
||||
{"Hospital or clinic name(s)"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
// autoFocus
|
||||
required
|
||||
placeholder="e.g. St. Mary's Hospital"
|
||||
{...field}
|
||||
// className="max-w-screen-sm h-10 bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="favorite_phrases"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full rounded-md">
|
||||
<FormLabel className="text-sm font-medium text-gray-700">
|
||||
{"Nurse's favorite phrases"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={3}
|
||||
placeholder={
|
||||
"e.g. 'You're doing great!' or 'Take a deep breath'"
|
||||
}
|
||||
{...field}
|
||||
// className="max-w-screen-sm bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="specialization"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full rounded-md">
|
||||
<FormLabel className="text-sm font-medium text-gray-700">
|
||||
{"Specializations and conditions treated"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={5}
|
||||
placeholder={
|
||||
"e.g. Pediatrician neurologist or Cardiologist treating heart conditions"
|
||||
}
|
||||
{...field}
|
||||
// className="max-w-screen-sm bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="hospital_layout"
|
||||
render={({ field }) => (
|
||||
<FormItem className="w-full rounded-md">
|
||||
<FormLabel className="text-sm font-medium text-gray-700">
|
||||
{"Hospital layout"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
rows={5}
|
||||
placeholder={
|
||||
"e.g. Our hospital has 2 floors. Main entrance leads to the lobby with reception desk straight ahead. \n\nFloor 1: Outpatient clinics and cafeteria. \nFloor 2: Surgery and recovery rooms. \n\nCafeteria is on the ground floor."
|
||||
}
|
||||
{...field}
|
||||
// className="max-w-screen-sm bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</section>
|
||||
<Button
|
||||
variant="default"
|
||||
className="rounded-full w-fit mt-4"
|
||||
size="sm"
|
||||
onClick={handleSave}
|
||||
type="submit"
|
||||
>
|
||||
Save settings
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
DoctorForm.displayName = "DoctorForm";
|
||||
|
||||
export default DoctorForm;
|
||||
|
|
@ -19,14 +19,17 @@ import {
|
|||
userFormPersonaPlaceholder,
|
||||
} from "@/lib/data";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
import { Loader2 } from "lucide-react";
|
||||
interface GeneralUserFormProps {
|
||||
selectedUser: IUser;
|
||||
heading: React.ReactNode;
|
||||
onSave?: (values: any, userType: "doctor" | "user") => void;
|
||||
selectedUser?: IUser;
|
||||
heading?: React.ReactNode;
|
||||
onSave?: (values: any, userType: "doctor" | "user", userId: string) => void;
|
||||
onClickCallback: () => void;
|
||||
userId: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
|
||||
export const UserSettingsSchema = z.object({
|
||||
supervisee_name: z.string().min(1).max(50),
|
||||
supervisee_age: z.number().min(1).max(18),
|
||||
|
|
@ -40,7 +43,7 @@ export const UserSettingsSchema = z.object({
|
|||
|
||||
export type GeneralUserInput = z.infer<typeof UserSettingsSchema>;
|
||||
|
||||
const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: GeneralUserFormProps) => {
|
||||
const GeneralUserForm = ({ selectedUser, onSave, onClickCallback, userId, heading, disabled }: GeneralUserFormProps) => {
|
||||
const form = useForm<GeneralUserInput>({
|
||||
defaultValues: {
|
||||
supervisee_name: selectedUser?.supervisee_name ?? "",
|
||||
|
|
@ -50,11 +53,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
|
|||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof UserSettingsSchema>) {
|
||||
onSave && onSave(values, "user");
|
||||
onSave && onSave(values, "user", userId);
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
onSave && onSave(form.getValues(), "user");
|
||||
onSave && onSave(form.getValues(), "user", userId);
|
||||
onClickCallback();
|
||||
};
|
||||
|
||||
|
|
@ -113,11 +116,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
|
|||
required
|
||||
placeholder="e.g. 8"
|
||||
{...field}
|
||||
// className="max-w-screen-sm h-10 bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
|
@ -139,11 +142,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
|
|||
userFormPersonaPlaceholder
|
||||
}
|
||||
{...field}
|
||||
// className="max-w-screen-sm bg-white"
|
||||
// autoComplete="on"
|
||||
// style={{
|
||||
// fontSize: 16,
|
||||
// }}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
|
|
@ -154,16 +157,17 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
|
|||
</section>
|
||||
<Button
|
||||
variant="default"
|
||||
className="rounded-full w-fit mt-4"
|
||||
className="rounded-full w-fit mt-4 flex flex-row items-center gap-2"
|
||||
size="sm"
|
||||
onClick={handleSave}
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
>
|
||||
Save settings
|
||||
{disabled ? <Loader2 className="w-4 h-4 animate-spin" /> : <span>Save settings</span>}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeneralUserForm;
|
||||
export default GeneralUserForm;
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
import { createClient } from "@/utils/supabase/server";
|
||||
import { getUserById } from "@/db/users";
|
||||
import Steps from "../components/Onboarding/Steps";
|
||||
|
||||
export default async function Home() {
|
||||
|
|
@ -9,11 +8,9 @@ export default async function Home() {
|
|||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
const dbUser = user ? await getUserById(supabase, user.id) : undefined;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
{dbUser && <Steps selectedUser={dbUser} />}
|
||||
<Steps userId={user?.id ?? ""} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { updateUser } from "./users";
|
||||
|
||||
export const dbCheckUserCode = async (
|
||||
supabase: SupabaseClient,
|
||||
userCode: string
|
||||
userCode: string,
|
||||
) => {
|
||||
const { data, error } = await supabase
|
||||
.from("devices")
|
||||
|
|
@ -19,9 +20,12 @@ export const dbCheckUserCode = async (
|
|||
export const updateDevice = async (
|
||||
supabase: SupabaseClient,
|
||||
device: Partial<IDevice>,
|
||||
device_id: string
|
||||
device_id: string,
|
||||
) => {
|
||||
const { error } = await supabase.from("devices").update(device).eq("device_id", device_id);
|
||||
const { error } = await supabase.from("devices").update(device).eq(
|
||||
"device_id",
|
||||
device_id,
|
||||
);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
|
@ -30,23 +34,29 @@ export const updateDevice = async (
|
|||
export const addUserToDevice = async (
|
||||
supabase: SupabaseClient,
|
||||
userCode: string,
|
||||
userId: string
|
||||
userId: string,
|
||||
) => {
|
||||
const { error } = await supabase
|
||||
const { data, error } = await supabase
|
||||
.from("devices")
|
||||
.update({ user_id: userId })
|
||||
.eq("user_code", userCode);
|
||||
.eq("user_code", userCode)
|
||||
.select("*")
|
||||
.maybeSingle();
|
||||
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data) {
|
||||
await updateUser(supabase, { device_id: data.device_id }, userId);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const doesUserHaveADevice = async (
|
||||
supabase: SupabaseClient,
|
||||
userId: string
|
||||
userId: string,
|
||||
) => {
|
||||
const { data, error } = await supabase
|
||||
.from("devices")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { type SupabaseClient, type User } from "@supabase/supabase-js";
|
|||
export const createUser = async (
|
||||
supabase: SupabaseClient,
|
||||
user: User,
|
||||
userProps: Partial<IUser>
|
||||
userProps: Partial<IUser>,
|
||||
) => {
|
||||
// console.log("creating user", user, userProps);
|
||||
|
||||
|
|
@ -18,9 +18,10 @@ export const createUser = async (
|
|||
supervisee_persona: "",
|
||||
personality_id: userProps.personality_id, // selecting default personality
|
||||
session_time: 0,
|
||||
avatar_url:
|
||||
user.user_metadata?.avatar_url ??
|
||||
`/user_avatar/user_avatar_${Math.floor(Math.random() * 10)}.png`,
|
||||
avatar_url: user.user_metadata?.avatar_url ??
|
||||
`/user_avatar/user_avatar_${
|
||||
Math.floor(Math.random() * 10)
|
||||
}.png`,
|
||||
} as IUser,
|
||||
]);
|
||||
|
||||
|
|
@ -31,7 +32,7 @@ export const createUser = async (
|
|||
|
||||
export const getSimpleUserById = async (
|
||||
supabase: SupabaseClient,
|
||||
id: string
|
||||
id: string,
|
||||
) => {
|
||||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
|
|
@ -40,7 +41,7 @@ export const getSimpleUserById = async (
|
|||
.single();
|
||||
|
||||
if (error) {
|
||||
console.log("error", error);
|
||||
console.log("error in getSimpleUserById", error);
|
||||
}
|
||||
|
||||
return data as IUser | undefined;
|
||||
|
|
@ -50,13 +51,13 @@ export const getUserById = async (supabase: SupabaseClient, id: string) => {
|
|||
const { data, error } = await supabase
|
||||
.from("users")
|
||||
.select(
|
||||
`*, personality:personality_id(*), device:devices!users_device_id_fkey(device_id, volume)`
|
||||
`*, personality:personality_id(*), device:devices!users_device_id_fkey(device_id, volume)`,
|
||||
)
|
||||
.eq("user_id", id)
|
||||
.single();
|
||||
|
||||
if (error) {
|
||||
console.log("error", error);
|
||||
console.log("error in getUserById", error);
|
||||
}
|
||||
|
||||
return data as IUser | undefined;
|
||||
|
|
@ -64,7 +65,7 @@ export const getUserById = async (supabase: SupabaseClient, id: string) => {
|
|||
|
||||
export const doesUserExist = async (
|
||||
supabase: SupabaseClient,
|
||||
authUser: User
|
||||
authUser: User,
|
||||
) => {
|
||||
const { data: user, error } = await supabase
|
||||
.from("users")
|
||||
|
|
@ -73,7 +74,7 @@ export const doesUserExist = async (
|
|||
.single();
|
||||
|
||||
if (error) {
|
||||
console.log("error", error);
|
||||
console.log("error in doesUserExist", error);
|
||||
}
|
||||
|
||||
return !!user;
|
||||
|
|
@ -82,7 +83,7 @@ export const doesUserExist = async (
|
|||
export const updateUser = async (
|
||||
supabase: SupabaseClient,
|
||||
user: Partial<IUser>,
|
||||
userId: string
|
||||
userId: string,
|
||||
) => {
|
||||
const { error } = await supabase
|
||||
.from("users")
|
||||
|
|
|
|||
Loading…
Reference in a new issue