fixed device registration and onboarding flow

This commit is contained in:
akdeb 2025-04-23 13:24:16 +01:00
parent c4743896a4
commit 953169d8ad
9 changed files with 128 additions and 468 deletions

View file

@ -4,9 +4,8 @@ import { encodedRedirect } from "@/utils/utils";
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { redirect } from "next/navigation"; import { redirect } from "next/navigation";
import { getMacAddressFromDeviceCode, isValidMacAddress } from "@/lib/utils";
import { addUserToDevice, dbCheckUserCode } from "@/db/devices"; import { addUserToDevice, dbCheckUserCode } from "@/db/devices";
import { getSimpleUserById, updateUser } from "@/db/users"; import { getSimpleUserById } from "@/db/users";
export async function deleteUserApiKey(userId: string) { export async function deleteUserApiKey(userId: string) {
const supabase = createClient(); const supabase = createClient();
@ -174,30 +173,3 @@ export const isPremiumUser = async (userId: string) => {
const dbUser = await getSimpleUserById(supabase, userId); const dbUser = await getSimpleUserById(supabase, userId);
return dbUser?.is_premium; 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 };
}

View file

@ -1,135 +1,57 @@
"use client"; "use client";
import { Progress } from "@/components/ui/progress"; import { Progress } from "@/components/ui/progress";
import { ArrowLeft, ArrowRight } from "lucide-react"; import React from "react";
import React, { useState } from "react";
import { Button } from "@/components/ui/button";
import UserType from "./UserType";
import GeneralUserForm from "../Settings/UserForm"; import GeneralUserForm from "../Settings/UserForm";
import DoctorForm from "../Settings/DoctorForm";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Label } from "@/components/ui/label"; import { createClient } from "@/utils/supabase/client";
import { Input } from "@/components/ui/input"; import { updateUser } from "@/db/users";
import { checkDoctorAction } from "@/app/actions"; import { Loader2 } from "lucide-react";
import { useToast } from "@/components/ui/use-toast";
type TUserType = "doctor" | "user" | "business";
const Steps: React.FC<{ const Steps: React.FC<{
selectedUser: IUser; selectedUser?: IUser;
}> = ({ selectedUser }) => { userId: string;
}> = ({ selectedUser, userId }) => {
const supabase = createClient();
const router = useRouter(); const router = useRouter();
const { toast } = useToast(); const [progress, setProgress] = React.useState(50);
const [progress, setProgress] = React.useState(40); const [step, setStep] = React.useState(1);
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 onClickFormCallback = async () => { 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); setStep(step + 1);
setProgress(progress + 30); setProgress(progress + 50);
router.push("/home"); router.push("/home");
}; };
const CurrentForm = () => { const CurrentForm = () => {
if (step === 0) { if (step === 1) {
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 = () => {
return ( return (
<div className="flex flex-col gap-4"> <GeneralUserForm
<div className="mt-4 text-center flex flex-row items-center justify-between gap-4"> selectedUser={selectedUser}
{step > 0 && ( userId={userId}
<Button onClickCallback={onClickFormCallback}
onClick={onClickBack} onSave={
variant="link" async (values, userType) => {
size="sm" await updateUser(
type="button" supabase,
className="mr-4 pl-0 flex flex-row items-center gap-2" {
> supervisee_age: values.supervisee_age,
<ArrowLeft size={16} /> Back supervisee_name: values.supervisee_name,
</Button> supervisee_persona: values.supervisee_persona,
)} user_info: {
{step > 0 && ( user_type: userType,
<Button user_metadata: values,
disabled={!selectedType} },
className="flex flex-row items-center gap-2" },
size="sm" userId);
type="submit" }}
> disabled={false}
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>
); );
} else {
return <Loader2 className="w-4 h-4 animate-spin" />;
}
}; };
let heading = "Let's get your Elato device & account set up"; 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."; "We want to make sure that your Elato is set up to provide you the best experience possible.";
if (step === 1) { 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!"; heading = "Hello there!";
subHeading = subHeading =
"With the following details we will be able to personalize your Elato experience."; "With the following details we will be able to personalize your Elato experience.";

View file

@ -24,12 +24,6 @@ const UserTypes: IUserType[] = [
title: "You are looking to use Elato for personal use", title: "You are looking to use Elato for personal use",
icon: <User />, icon: <User />,
}, },
{
type: "doctor",
name: "Doctor",
title: "You are a licensed doctor or physician",
icon: <Hospital />,
},
{ {
type: "business", type: "business",
name: "Business", name: "Business",
@ -40,10 +34,9 @@ const UserTypes: IUserType[] = [
]; ];
const UserType: React.FC<{ const UserType: React.FC<{
selectedUser: IUser;
selectedType: TUserType | null; selectedType: TUserType | null;
onSelectType: (type: TUserType) => void; onSelectType: (type: TUserType) => void;
}> = ({ selectedType, onSelectType, selectedUser }) => { }> = ({ selectedType, onSelectType }) => {
const onPickType = async (userType: IUserType) => { const onPickType = async (userType: IUserType) => {
if (!userType.disabled) { if (!userType.disabled) {
onSelectType(userType.type); onSelectType(userType.type);
@ -55,7 +48,7 @@ const UserType: React.FC<{
<h1 className="text-xl font-medium my-4 inline"> <h1 className="text-xl font-medium my-4 inline">
You are a <ArrowRight className="inline-block" />{" "} You are a <ArrowRight className="inline-block" />{" "}
</h1> </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) => ( {UserTypes.map((userType) => (
<Card <Card
key={userType.type} key={userType.type}
@ -99,5 +92,3 @@ const UserType: React.FC<{
</div> </div>
); );
}; };
export default UserType;

View file

@ -1,9 +1,10 @@
import { registerDevice, signOutAction } from "@/app/actions";
import { connectUserToDevice, signOutAction } from "@/app/actions";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { LogOut } from "lucide-react"; import { LogOut } from "lucide-react";
import DoctorForm from "./DoctorForm";
import GeneralUserForm from "./UserForm"; import GeneralUserForm from "./UserForm";
import { Slider } from "@/components/ui/slider"; import { Slider } from "@/components/ui/slider";
import { updateUser } from "@/db/users"; import { updateUser } from "@/db/users";
@ -12,12 +13,6 @@ import { createClient } from "@/utils/supabase/client";
import React, { useCallback } from "react"; import React, { useCallback } from "react";
import { doesUserHaveADevice, updateDevice } from "@/db/devices"; import { doesUserHaveADevice, updateDevice } from "@/db/devices";
import { useToast } from "@/components/ui/use-toast"; import { useToast } from "@/components/ui/use-toast";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider,
} from "@/components/ui/tooltip";
interface AppSettingsProps { interface AppSettingsProps {
selectedUser: IUser; selectedUser: IUser;
@ -50,6 +45,11 @@ const AppSettings: React.FC<AppSettingsProps> = ({
); );
}, [selectedUser.user_id, supabase]); }, [selectedUser.user_id, supabase]);
React.useEffect(() => {
checkIfUserHasDevice();
}, [checkIfUserHasDevice]);
const [volume, setVolume] = React.useState([ const [volume, setVolume] = React.useState([
selectedUser.device?.volume ?? 50, selectedUser.device?.volume ?? 50,
]); ]);
@ -69,7 +69,8 @@ const AppSettings: React.FC<AppSettingsProps> = ({
debouncedUpdateVolume(); debouncedUpdateVolume();
}; };
const onSave = async (values: any, userType: "doctor" | "user") => { const onSave = async (values: any, userType: "doctor" | "user", userId: string) => {
await updateUser( await updateUser(
supabase, supabase,
{ {
@ -81,29 +82,22 @@ const AppSettings: React.FC<AppSettingsProps> = ({
user_metadata: values, user_metadata: values,
}, },
}, },
selectedUser!.user_id); userId);
toast({ toast({
description: "Your prefereces have been saved!", description: "Your prefereces have been saved!",
}); });
} }
return ( return (
<> <>
{selectedUser.user_info.user_type === "doctor" ? ( <GeneralUserForm
<DoctorForm
selectedUser={selectedUser} selectedUser={selectedUser}
userId={selectedUser.user_id}
heading={heading} heading={heading}
onSave={onSave} onSave={onSave}
onClickCallback={() => handleSave()} onClickCallback={() => handleSave()}
/> />
) : (
<GeneralUserForm
selectedUser={selectedUser}
heading={heading}
onSave={onSave}
onClickCallback={() => handleSave()}
/>
)}
<section className="space-y-4 max-w-screen-sm mt-12"> <section className="space-y-4 max-w-screen-sm mt-12">
<h2 className="text-lg font-semibold border-b border-gray-200 pb-2"> <h2 className="text-lg font-semibold border-b border-gray-200 pb-2">
Device settings Device settings
@ -112,18 +106,8 @@ const AppSettings: React.FC<AppSettingsProps> = ({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-row items-center gap-2"> <div className="flex flex-row items-center gap-2">
<Label className="text-sm font-medium text-gray-700"> <Label className="text-sm font-medium text-gray-700">
Register your device Register your device
</Label> </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>
<div <div
className={`rounded-full flex-shrink-0 h-2 w-2 ${ className={`rounded-full flex-shrink-0 h-2 w-2 ${
isConnected ? 'bg-green-500' : 'bg-amber-500' isConnected ? 'bg-green-500' : 'bg-amber-500'
@ -137,16 +121,17 @@ const AppSettings: React.FC<AppSettingsProps> = ({
value={deviceCode} value={deviceCode}
disabled={isConnected} disabled={isConnected}
onChange={(e) => setDeviceCode(e.target.value)} onChange={(e) => setDeviceCode(e.target.value)}
placeholder={isConnected ? "**********" : "Enter your ESP32-S3 MAC address"} placeholder={isConnected ? "**********" : "Enter your device code"}
maxLength={100}
/> />
<Button <Button
size="sm" size="sm"
variant="outline" variant="outline"
disabled={isConnected} disabled={isConnected}
onClick={async () => { onClick={async () => {
const result = await registerDevice(selectedUser.user_id, deviceCode); const result = await connectUserToDevice(selectedUser.user_id, deviceCode);
if (result.error) { if (!result) {
setError(result.error); setError("Error registering device");
} }
checkIfUserHasDevice(); checkIfUserHasDevice();
}} }}
@ -157,10 +142,10 @@ const AppSettings: React.FC<AppSettingsProps> = ({
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
{isConnected ? <span className="font-medium text-gray-800">Registered!</span> : {isConnected ? <span className="font-medium text-gray-800">Registered!</span> :
error ? <span className="text-red-500">{error}.</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> </p>
</div> </div>
<div className="flex flex-col gap-2 mt-2"> <div className="flex flex-col gap-2 mt-2">
<Label className="text-sm font-medium text-gray-700"> <Label className="text-sm font-medium text-gray-700">
Logged in as Logged in as

View file

@ -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;

View file

@ -19,14 +19,17 @@ import {
userFormPersonaPlaceholder, userFormPersonaPlaceholder,
} from "@/lib/data"; } from "@/lib/data";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Loader2 } from "lucide-react";
interface GeneralUserFormProps { interface GeneralUserFormProps {
selectedUser: IUser; selectedUser?: IUser;
heading: React.ReactNode; heading?: React.ReactNode;
onSave?: (values: any, userType: "doctor" | "user") => void; onSave?: (values: any, userType: "doctor" | "user", userId: string) => void;
onClickCallback: () => void; onClickCallback: () => void;
userId: string;
disabled?: boolean;
} }
export const UserSettingsSchema = z.object({ export const UserSettingsSchema = z.object({
supervisee_name: z.string().min(1).max(50), supervisee_name: z.string().min(1).max(50),
supervisee_age: z.number().min(1).max(18), supervisee_age: z.number().min(1).max(18),
@ -40,7 +43,7 @@ export const UserSettingsSchema = z.object({
export type GeneralUserInput = z.infer<typeof UserSettingsSchema>; 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>({ const form = useForm<GeneralUserInput>({
defaultValues: { defaultValues: {
supervisee_name: selectedUser?.supervisee_name ?? "", supervisee_name: selectedUser?.supervisee_name ?? "",
@ -50,11 +53,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
}); });
async function onSubmit(values: z.infer<typeof UserSettingsSchema>) { async function onSubmit(values: z.infer<typeof UserSettingsSchema>) {
onSave && onSave(values, "user"); onSave && onSave(values, "user", userId);
} }
const handleSave = () => { const handleSave = () => {
onSave && onSave(form.getValues(), "user"); onSave && onSave(form.getValues(), "user", userId);
onClickCallback(); onClickCallback();
}; };
@ -113,11 +116,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
required required
placeholder="e.g. 8" placeholder="e.g. 8"
{...field} {...field}
// className="max-w-screen-sm h-10 bg-white"
// autoComplete="on"
// style={{
// fontSize: 16,
// }}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -139,11 +142,11 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
userFormPersonaPlaceholder userFormPersonaPlaceholder
} }
{...field} {...field}
// className="max-w-screen-sm bg-white"
// autoComplete="on"
// style={{
// fontSize: 16,
// }}
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
@ -154,12 +157,13 @@ const GeneralUserForm = ({ selectedUser, heading, onSave, onClickCallback }: Gen
</section> </section>
<Button <Button
variant="default" 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" size="sm"
onClick={handleSave} onClick={handleSave}
type="submit" type="submit"
disabled={disabled}
> >
Save settings {disabled ? <Loader2 className="w-4 h-4 animate-spin" /> : <span>Save settings</span>}
</Button> </Button>
</form> </form>
</Form> </Form>

View file

@ -1,5 +1,4 @@
import { createClient } from "@/utils/supabase/server"; import { createClient } from "@/utils/supabase/server";
import { getUserById } from "@/db/users";
import Steps from "../components/Onboarding/Steps"; import Steps from "../components/Onboarding/Steps";
export default async function Home() { export default async function Home() {
@ -9,11 +8,9 @@ export default async function Home() {
data: { user }, data: { user },
} = await supabase.auth.getUser(); } = await supabase.auth.getUser();
const dbUser = user ? await getUserById(supabase, user.id) : undefined;
return ( return (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{dbUser && <Steps selectedUser={dbUser} />} <Steps userId={user?.id ?? ""} />
</div> </div>
); );
} }

View file

@ -1,8 +1,9 @@
import { SupabaseClient } from "@supabase/supabase-js"; import { SupabaseClient } from "@supabase/supabase-js";
import { updateUser } from "./users";
export const dbCheckUserCode = async ( export const dbCheckUserCode = async (
supabase: SupabaseClient, supabase: SupabaseClient,
userCode: string userCode: string,
) => { ) => {
const { data, error } = await supabase const { data, error } = await supabase
.from("devices") .from("devices")
@ -19,9 +20,12 @@ export const dbCheckUserCode = async (
export const updateDevice = async ( export const updateDevice = async (
supabase: SupabaseClient, supabase: SupabaseClient,
device: Partial<IDevice>, 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) { if (error) {
throw error; throw error;
} }
@ -30,23 +34,29 @@ export const updateDevice = async (
export const addUserToDevice = async ( export const addUserToDevice = async (
supabase: SupabaseClient, supabase: SupabaseClient,
userCode: string, userCode: string,
userId: string userId: string,
) => { ) => {
const { error } = await supabase const { data, error } = await supabase
.from("devices") .from("devices")
.update({ user_id: userId }) .update({ user_id: userId })
.eq("user_code", userCode); .eq("user_code", userCode)
.select("*")
.maybeSingle();
if (error) { if (error) {
return false; return false;
} }
if (data) {
await updateUser(supabase, { device_id: data.device_id }, userId);
}
return true; return true;
}; };
export const doesUserHaveADevice = async ( export const doesUserHaveADevice = async (
supabase: SupabaseClient, supabase: SupabaseClient,
userId: string userId: string,
) => { ) => {
const { data, error } = await supabase const { data, error } = await supabase
.from("devices") .from("devices")

View file

@ -3,7 +3,7 @@ import { type SupabaseClient, type User } from "@supabase/supabase-js";
export const createUser = async ( export const createUser = async (
supabase: SupabaseClient, supabase: SupabaseClient,
user: User, user: User,
userProps: Partial<IUser> userProps: Partial<IUser>,
) => { ) => {
// console.log("creating user", user, userProps); // console.log("creating user", user, userProps);
@ -18,9 +18,10 @@ export const createUser = async (
supervisee_persona: "", supervisee_persona: "",
personality_id: userProps.personality_id, // selecting default personality personality_id: userProps.personality_id, // selecting default personality
session_time: 0, session_time: 0,
avatar_url: avatar_url: user.user_metadata?.avatar_url ??
user.user_metadata?.avatar_url ?? `/user_avatar/user_avatar_${
`/user_avatar/user_avatar_${Math.floor(Math.random() * 10)}.png`, Math.floor(Math.random() * 10)
}.png`,
} as IUser, } as IUser,
]); ]);
@ -31,7 +32,7 @@ export const createUser = async (
export const getSimpleUserById = async ( export const getSimpleUserById = async (
supabase: SupabaseClient, supabase: SupabaseClient,
id: string id: string,
) => { ) => {
const { data, error } = await supabase const { data, error } = await supabase
.from("users") .from("users")
@ -40,7 +41,7 @@ export const getSimpleUserById = async (
.single(); .single();
if (error) { if (error) {
console.log("error", error); console.log("error in getSimpleUserById", error);
} }
return data as IUser | undefined; return data as IUser | undefined;
@ -50,13 +51,13 @@ export const getUserById = async (supabase: SupabaseClient, id: string) => {
const { data, error } = await supabase const { data, error } = await supabase
.from("users") .from("users")
.select( .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) .eq("user_id", id)
.single(); .single();
if (error) { if (error) {
console.log("error", error); console.log("error in getUserById", error);
} }
return data as IUser | undefined; return data as IUser | undefined;
@ -64,7 +65,7 @@ export const getUserById = async (supabase: SupabaseClient, id: string) => {
export const doesUserExist = async ( export const doesUserExist = async (
supabase: SupabaseClient, supabase: SupabaseClient,
authUser: User authUser: User,
) => { ) => {
const { data: user, error } = await supabase const { data: user, error } = await supabase
.from("users") .from("users")
@ -73,7 +74,7 @@ export const doesUserExist = async (
.single(); .single();
if (error) { if (error) {
console.log("error", error); console.log("error in doesUserExist", error);
} }
return !!user; return !!user;
@ -82,7 +83,7 @@ export const doesUserExist = async (
export const updateUser = async ( export const updateUser = async (
supabase: SupabaseClient, supabase: SupabaseClient,
user: Partial<IUser>, user: Partial<IUser>,
userId: string userId: string,
) => { ) => {
const { error } = await supabase const { error } = await supabase
.from("users") .from("users")