Just making a mess mostly I think
This commit is contained in:
@ -10,7 +10,6 @@ import React, {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import {
|
import {
|
||||||
getProfile,
|
getProfile,
|
||||||
getSignedUrl,
|
|
||||||
getUser,
|
getUser,
|
||||||
updateProfile as updateProfileAction,
|
updateProfile as updateProfileAction,
|
||||||
} from '@/lib/hooks';
|
} from '@/lib/hooks';
|
||||||
@ -20,7 +19,6 @@ import { toast } from 'sonner';
|
|||||||
type AuthContextType = {
|
type AuthContextType = {
|
||||||
user: User | null;
|
user: User | null;
|
||||||
profile: Profile | null;
|
profile: Profile | null;
|
||||||
avatarUrl: string | null;
|
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
updateProfile: (data: {
|
updateProfile: (data: {
|
||||||
@ -36,7 +34,6 @@ const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|||||||
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [user, setUser] = useState<User | null>(null);
|
const [user, setUser] = useState<User | null>(null);
|
||||||
const [profile, setProfile] = useState<Profile | null>(null);
|
const [profile, setProfile] = useState<Profile | null>(null);
|
||||||
const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const [isInitialized, setIsInitialized] = useState(false);
|
||||||
const fetchingRef = useRef(false);
|
const fetchingRef = useRef(false);
|
||||||
@ -58,27 +55,11 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
if (!userResponse.success || !profileResponse.success) {
|
if (!userResponse.success || !profileResponse.success) {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setProfile(null);
|
setProfile(null);
|
||||||
setAvatarUrl(null);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setUser(userResponse.data);
|
setUser(userResponse.data);
|
||||||
setProfile(profileResponse.data);
|
setProfile(profileResponse.data);
|
||||||
|
|
||||||
// Get avatar URL if available
|
|
||||||
if (profileResponse.data.avatar_url) {
|
|
||||||
const avatarResponse = await getSignedUrl({
|
|
||||||
bucket: 'avatars',
|
|
||||||
url: profileResponse.data.avatar_url,
|
|
||||||
});
|
|
||||||
if (avatarResponse.success) {
|
|
||||||
setAvatarUrl(avatarResponse.data);
|
|
||||||
} else {
|
|
||||||
setAvatarUrl(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setAvatarUrl(null);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
'Auth fetch error: ',
|
'Auth fetch error: ',
|
||||||
@ -118,7 +99,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
} else if (event === 'SIGNED_OUT') {
|
} else if (event === 'SIGNED_OUT') {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setProfile(null);
|
setProfile(null);
|
||||||
setAvatarUrl(null);
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else if (event === 'TOKEN_REFRESHED') {
|
} else if (event === 'TOKEN_REFRESHED') {
|
||||||
// Silent refresh - don't show loading
|
// Silent refresh - don't show loading
|
||||||
@ -158,7 +138,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
} else if (event === 'SIGNED_OUT') {
|
} else if (event === 'SIGNED_OUT') {
|
||||||
setUser(null);
|
setUser(null);
|
||||||
setProfile(null);
|
setProfile(null);
|
||||||
setAvatarUrl(null);
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else if (event === 'TOKEN_REFRESHED') {
|
} else if (event === 'TOKEN_REFRESHED') {
|
||||||
console.log('Token refreshed, updating user data');
|
console.log('Token refreshed, updating user data');
|
||||||
@ -184,18 +163,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
throw new Error(result.error ?? 'Failed to update profile');
|
throw new Error(result.error ?? 'Failed to update profile');
|
||||||
}
|
}
|
||||||
setProfile(result.data);
|
setProfile(result.data);
|
||||||
|
|
||||||
// If avatar was updated, refresh the avatar URL
|
|
||||||
if (data.avatar_url && result.data.avatar_url) {
|
|
||||||
const avatarResponse = await getSignedUrl({
|
|
||||||
bucket: 'avatars',
|
|
||||||
url: result.data.avatar_url,
|
|
||||||
transform: { width: 128, height: 128 },
|
|
||||||
});
|
|
||||||
if (avatarResponse.success) {
|
|
||||||
setAvatarUrl(avatarResponse.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toast.success('Profile updated successfully!');
|
toast.success('Profile updated successfully!');
|
||||||
return { success: true, data: result.data };
|
return { success: true, data: result.data };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -216,7 +183,6 @@ export const AuthProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
const value = {
|
const value = {
|
||||||
user,
|
user,
|
||||||
profile,
|
profile,
|
||||||
avatarUrl,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
isAuthenticated: !!user,
|
isAuthenticated: !!user,
|
||||||
updateProfile,
|
updateProfile,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { signInWithApple } from '@/lib/actions';
|
import { signInWithApple, getProfile, updateProfile } from '@/lib/actions';
|
||||||
import { StatusMessage, SubmitButton } from '@/components/default';
|
import { StatusMessage, SubmitButton } from '@/components/default';
|
||||||
import { useAuth } from '@/components/context';
|
import { useAuth } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
@ -34,8 +34,23 @@ export const SignInWithApple = ({
|
|||||||
const result = await signInWithApple();
|
const result = await signInWithApple();
|
||||||
|
|
||||||
if (result?.success && result.data) {
|
if (result?.success && result.data) {
|
||||||
|
const profileResponse = await getProfile();
|
||||||
|
if (profileResponse.success) {
|
||||||
|
const profile = profileResponse.data;
|
||||||
|
if (!profile.provider) {
|
||||||
|
const updateResponse = await updateProfile({
|
||||||
|
provider: result.data.provider,
|
||||||
|
})
|
||||||
|
if (!updateResponse.success) throw new Error('Could not update provider!');
|
||||||
|
} else {
|
||||||
|
const updateResponse = await updateProfile({
|
||||||
|
provider: profile.provider + ' ' + result.data.provider,
|
||||||
|
})
|
||||||
|
if (!updateResponse.success) throw new Error('Could not update provider!');
|
||||||
|
}
|
||||||
|
}
|
||||||
// Redirect to Apple OAuth page
|
// Redirect to Apple OAuth page
|
||||||
window.location.href = result.data;
|
window.location.href = result.data.url;
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage(`Error signing in with Apple!`);
|
setStatusMessage(`Error signing in with Apple!`);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import Image from 'next/image';
|
|||||||
import { type buttonVariants } from '@/components/ui';
|
import { type buttonVariants } from '@/components/ui';
|
||||||
import { type ComponentProps } from 'react';
|
import { type ComponentProps } from 'react';
|
||||||
import { type VariantProps } from 'class-variance-authority';
|
import { type VariantProps } from 'class-variance-authority';
|
||||||
|
import { getProfile, updateProfile } from '@/lib/hooks';
|
||||||
|
|
||||||
type SignInWithMicrosoftProps = {
|
type SignInWithMicrosoftProps = {
|
||||||
className?: ComponentProps<'div'>['className'];
|
className?: ComponentProps<'div'>['className'];
|
||||||
@ -32,8 +33,22 @@ export const SignInWithMicrosoft = ({
|
|||||||
const result = await signInWithMicrosoft();
|
const result = await signInWithMicrosoft();
|
||||||
|
|
||||||
if (result?.success && result.data) {
|
if (result?.success && result.data) {
|
||||||
// Redirect to Microsoft OAuth page
|
const profileResponse = await getProfile();
|
||||||
window.location.href = result.data;
|
if (profileResponse.success) {
|
||||||
|
const profile = profileResponse.data;
|
||||||
|
if (!profile.provider) {
|
||||||
|
const updateResponse = await updateProfile({
|
||||||
|
provider: result.data.provider,
|
||||||
|
})
|
||||||
|
if (!updateResponse.success) throw new Error('Could not update provider!');
|
||||||
|
} else {
|
||||||
|
const updateResponse = await updateProfile({
|
||||||
|
provider: profile.provider + ' ' + result.data.provider,
|
||||||
|
})
|
||||||
|
if (!updateResponse.success) throw new Error('Could not update provider!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.location.href = result.data.url;
|
||||||
} else {
|
} else {
|
||||||
setStatusMessage(`Error: Could not sign in with Microsoft!`);
|
setStatusMessage(`Error: Could not sign in with Microsoft!`);
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
|
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
BasedAvatar,
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
@ -15,10 +13,9 @@ import {
|
|||||||
import { useAuth, useTVMode } from '@/components/context';
|
import { useAuth, useTVMode } from '@/components/context';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { signOut } from '@/lib/actions';
|
import { signOut } from '@/lib/actions';
|
||||||
import { User } from 'lucide-react';
|
|
||||||
|
|
||||||
const AvatarDropdown = () => {
|
const AvatarDropdown = () => {
|
||||||
const { profile, avatarUrl, refreshUserData } = useAuth();
|
const { profile, refreshUserData } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { toggleTVMode, tvMode } = useTVMode();
|
const { toggleTVMode, tvMode } = useTVMode();
|
||||||
|
|
||||||
@ -30,36 +27,16 @@ const AvatarDropdown = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInitials = (name: string | null | undefined): string => {
|
|
||||||
if (!name) return '';
|
|
||||||
return name
|
|
||||||
.split(' ')
|
|
||||||
.map((n) => n[0])
|
|
||||||
.join('')
|
|
||||||
.toUpperCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<Avatar className='cursor-pointer scale-125'>
|
<BasedAvatar
|
||||||
{avatarUrl ? (
|
src={profile?.avatar_url}
|
||||||
<AvatarImage
|
fullName={profile?.full_name}
|
||||||
src={avatarUrl}
|
className='h-12 w-12 my-auto'
|
||||||
alt={getInitials(profile?.full_name)}
|
fallbackClassName='text-xl font-semibold'
|
||||||
width={64}
|
userIconSize={32}
|
||||||
height={64}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AvatarFallback className='text-md'>
|
|
||||||
{profile?.full_name ? (
|
|
||||||
getInitials(profile.full_name)
|
|
||||||
) : (
|
|
||||||
<User size={64} />
|
|
||||||
)}
|
|
||||||
</AvatarFallback>
|
|
||||||
)}
|
|
||||||
</Avatar>
|
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuLabel className='font-bold'>
|
<DropdownMenuLabel className='font-bold'>
|
||||||
|
@ -11,16 +11,17 @@ const Header = () => {
|
|||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
return tvMode ? (
|
return tvMode ? (
|
||||||
<div className='w-full py-2 pt-6 md:py-5'>
|
<div className='w-full py-2 pt-6 md:py-5'>
|
||||||
<div className='absolute top-8 right-24'>
|
<div className='absolute top-8 right-16'>
|
||||||
<div className='flex flex-row my-auto items-center pt-2 pr-0 md:pt-4'>
|
<div className='flex flex-row my-auto items-center'>
|
||||||
|
<ThemeToggle className='mr-4' />
|
||||||
{isAuthenticated && <AvatarDropdown />}
|
{isAuthenticated && <AvatarDropdown />}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<header className='w-full py-2 pt-6 md:py-5'>
|
<header className='w-full py-2 md:py-5'>
|
||||||
<div className='absolute top-8 right-16'>
|
<div className='absolute top-8 right-16'>
|
||||||
<div className='flex flex-row my-auto items-center pt-2 pr-0 md:pt-4 md:pr-8'>
|
<div className='flex flex-row my-auto items-center'>
|
||||||
<ThemeToggle className='mr-4' />
|
<ThemeToggle className='mr-4' />
|
||||||
{isAuthenticated && <AvatarDropdown />}
|
{isAuthenticated && <AvatarDropdown />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import { useFileUpload } from '@/lib/hooks/useFileUpload';
|
import { useFileUpload } from '@/lib/hooks/useFileUpload';
|
||||||
import { useAuth } from '@/components/context';
|
import { useAuth } from '@/components/context';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
BasedAvatar,
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
CardContent,
|
CardContent,
|
||||||
} from '@/components/ui';
|
} from '@/components/ui';
|
||||||
import { Loader2, Pencil, Upload, User } from 'lucide-react';
|
import { Loader2, Pencil, Upload } from 'lucide-react';
|
||||||
|
|
||||||
type AvatarUploadProps = {
|
type AvatarUploadProps = {
|
||||||
onAvatarUploaded: (path: string) => Promise<void>;
|
onAvatarUploaded: (path: string) => Promise<void>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
||||||
const { profile, avatarUrl } = useAuth();
|
const { profile } = useAuth();
|
||||||
const { isUploading, fileInputRef, uploadToStorage } = useFileUpload();
|
const { isUploading, fileInputRef, uploadToStorage } = useFileUpload();
|
||||||
|
|
||||||
const handleAvatarClick = () => {
|
const handleAvatarClick = () => {
|
||||||
@ -40,15 +38,6 @@ export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInitials = (name: string | null | undefined): string => {
|
|
||||||
if (!name) return '';
|
|
||||||
return name
|
|
||||||
.split(' ')
|
|
||||||
.map((n) => n[0])
|
|
||||||
.join('')
|
|
||||||
.toUpperCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className='flex flex-col items-center'>
|
<div className='flex flex-col items-center'>
|
||||||
@ -56,24 +45,13 @@ export const AvatarUpload = ({ onAvatarUploaded }: AvatarUploadProps) => {
|
|||||||
className='relative group cursor-pointer mb-4'
|
className='relative group cursor-pointer mb-4'
|
||||||
onClick={handleAvatarClick}
|
onClick={handleAvatarClick}
|
||||||
>
|
>
|
||||||
<Avatar className='h-32 w-32'>
|
<BasedAvatar
|
||||||
{avatarUrl ? (
|
src={profile?.avatar_url}
|
||||||
<AvatarImage
|
fullName={profile?.full_name}
|
||||||
src={avatarUrl}
|
className='h-32 w-32'
|
||||||
alt={getInitials(profile?.full_name)}
|
fallbackClassName='text-4xl font-semibold'
|
||||||
width={128}
|
userIconSize={100}
|
||||||
height={128}
|
/>
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<AvatarFallback className='text-4xl'>
|
|
||||||
{profile?.full_name ? (
|
|
||||||
getInitials(profile.full_name)
|
|
||||||
) : (
|
|
||||||
<User size={32} />
|
|
||||||
)}
|
|
||||||
</AvatarFallback>
|
|
||||||
)}
|
|
||||||
</Avatar>
|
|
||||||
<div
|
<div
|
||||||
className='absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/50
|
className='absolute inset-0 rounded-full bg-black/0 group-hover:bg-black/50
|
||||||
transition-all flex items-center justify-center'
|
transition-all flex items-center justify-center'
|
||||||
|
@ -5,17 +5,12 @@ import { useState, useEffect, useCallback, useRef } from 'react';
|
|||||||
import { useAuth, useTVMode } from '@/components/context';
|
import { useAuth, useTVMode } from '@/components/context';
|
||||||
import {
|
import {
|
||||||
getRecentUsersWithStatuses,
|
getRecentUsersWithStatuses,
|
||||||
getSignedUrl,
|
|
||||||
updateStatuses,
|
updateStatuses,
|
||||||
updateUserStatus,
|
updateUserStatus,
|
||||||
type UserWithStatus,
|
type UserWithStatus,
|
||||||
} from '@/lib/hooks';
|
} from '@/lib/hooks';
|
||||||
import {
|
import {
|
||||||
Avatar,
|
BasedAvatar,
|
||||||
AvatarImage,
|
|
||||||
AvatarFallback,
|
|
||||||
BasedAvatarImage,
|
|
||||||
BasedAvatarFallback,
|
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerTrigger,
|
DrawerTrigger,
|
||||||
Loading
|
Loading
|
||||||
@ -26,7 +21,6 @@ import { HistoryDrawer } from '@/components/status';
|
|||||||
import type { Profile } from '@/utils/supabase';
|
import type { Profile } from '@/utils/supabase';
|
||||||
import type { RealtimeChannel } from '@supabase/supabase-js';
|
import type { RealtimeChannel } from '@supabase/supabase-js';
|
||||||
import { makeConditionalClassName } from '@/lib/utils';
|
import { makeConditionalClassName } from '@/lib/utils';
|
||||||
import { User } from 'lucide-react';
|
|
||||||
|
|
||||||
type TechTableProps = {
|
type TechTableProps = {
|
||||||
initialStatuses: UserWithStatus[];
|
initialStatuses: UserWithStatus[];
|
||||||
@ -58,22 +52,6 @@ export const TechTable = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const fetchAvatarUrl = useCallback(async (url: string) => {
|
|
||||||
try {
|
|
||||||
const avatarResponse = await getSignedUrl({
|
|
||||||
bucket: 'avatars',
|
|
||||||
url,
|
|
||||||
transform: { width: 128, height: 128 },
|
|
||||||
});
|
|
||||||
if (!avatarResponse.success) {
|
|
||||||
throw new Error(avatarResponse.error);
|
|
||||||
}
|
|
||||||
return avatarResponse.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching avatar URL:', error);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Initial load
|
// Initial load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadData = async () => {
|
const loadData = async () => {
|
||||||
@ -289,23 +267,11 @@ export const TechTable = ({
|
|||||||
)}
|
)}
|
||||||
<td className={tdClassName}>
|
<td className={tdClassName}>
|
||||||
<div className='flex'>
|
<div className='flex'>
|
||||||
<Avatar>
|
<BasedAvatar
|
||||||
{userWithStatus.user.avatar_url ? (
|
src={userWithStatus.user.avatar_url}
|
||||||
<BasedAvatarImage
|
fullName={userWithStatus.user.full_name}
|
||||||
src={userWithStatus.avatar_url}
|
/>
|
||||||
fullName={userWithStatus.user.full_name ?? ''}
|
|
||||||
width={64}
|
|
||||||
height={64}
|
|
||||||
/>
|
|
||||||
): (
|
|
||||||
<BasedAvatarFallback
|
|
||||||
className='text-md'
|
|
||||||
fullName={userWithStatus.user.full_name}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Avatar>
|
|
||||||
<p>{userWithStatus.user.full_name ?? 'Unknown User'}</p>
|
<p>{userWithStatus.user.full_name ?? 'Unknown User'}</p>
|
||||||
<p>{userWithStatus.avatar_url}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className={tdClassName}>
|
<td className={tdClassName}>
|
||||||
|
@ -3,9 +3,63 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
||||||
import { User } from 'lucide-react';
|
import { User } from 'lucide-react';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
|
||||||
|
type BasedAvatarProps = React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
||||||
|
src?: string | null;
|
||||||
|
fullName?: string | null;
|
||||||
|
imageClassName?: string;
|
||||||
|
fallbackClassName?: string;
|
||||||
|
userIconSize?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function BasedAvatar({
|
||||||
|
src = null,
|
||||||
|
fullName = null,
|
||||||
|
imageClassName ='',
|
||||||
|
fallbackClassName = '',
|
||||||
|
userIconSize = 32,
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: BasedAvatarProps) {
|
||||||
|
return (
|
||||||
|
<AvatarPrimitive.Root
|
||||||
|
data-slot='avatar'
|
||||||
|
className={cn(
|
||||||
|
'cursor-pointer relative flex size-8 shrink-0 overflow-hidden rounded-full',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{src ? (
|
||||||
|
<AvatarImage
|
||||||
|
src={src}
|
||||||
|
className={imageClassName}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<AvatarPrimitive.Fallback
|
||||||
|
data-slot='avatar-fallback'
|
||||||
|
className={cn(
|
||||||
|
'bg-muted flex size-full items-center justify-center rounded-full',
|
||||||
|
fallbackClassName,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{fullName ? (
|
||||||
|
fullName
|
||||||
|
.split(' ')
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join('')
|
||||||
|
.toUpperCase()
|
||||||
|
) : (
|
||||||
|
<User size={userIconSize} />
|
||||||
|
)}
|
||||||
|
</AvatarPrimitive.Fallback>
|
||||||
|
)}
|
||||||
|
</AvatarPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Avatar({
|
function Avatar({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
@ -22,25 +76,6 @@ function Avatar({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasedAvatarImageProps = React.ComponentProps<typeof AvatarPrimitive.Image> & {
|
|
||||||
fullName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function BasedAvatarImage({
|
|
||||||
fullName,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: BasedAvatarImageProps) {
|
|
||||||
return (
|
|
||||||
<AvatarPrimitive.Image
|
|
||||||
data-slot='avatar-image'
|
|
||||||
className={cn('aspect-square size-full', className)}
|
|
||||||
alt={fullName.split(' ').map((n) => n[0]).join('').toUpperCase()}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AvatarImage({
|
function AvatarImage({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
@ -54,38 +89,6 @@ function AvatarImage({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type BasedAvatarFallbackProps =
|
|
||||||
React.ComponentProps<typeof AvatarPrimitive.Fallback> & {
|
|
||||||
fullName?: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
function BasedAvatarFallback({
|
|
||||||
fullName = null,
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: BasedAvatarFallbackProps) {
|
|
||||||
return (
|
|
||||||
<AvatarPrimitive.Fallback
|
|
||||||
data-slot='avatar-fallback'
|
|
||||||
className={cn(
|
|
||||||
'bg-muted flex size-full items-center justify-center rounded-full',
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{fullName ? (
|
|
||||||
fullName
|
|
||||||
.split(' ')
|
|
||||||
.map((n) => n[0])
|
|
||||||
.join('')
|
|
||||||
.toUpperCase()
|
|
||||||
) : (
|
|
||||||
<User size={64} />
|
|
||||||
)}
|
|
||||||
</AvatarPrimitive.Fallback>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function AvatarFallback({
|
function AvatarFallback({
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
@ -102,4 +105,4 @@ function AvatarFallback({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Avatar, AvatarImage, BasedAvatarImage, AvatarFallback, BasedAvatarFallback };
|
export { Avatar, BasedAvatar, AvatarImage, AvatarFallback };
|
||||||
|
@ -3,8 +3,7 @@
|
|||||||
import 'server-only';
|
import 'server-only';
|
||||||
import { createServerClient } from '@/utils/supabase';
|
import { createServerClient } from '@/utils/supabase';
|
||||||
import { headers } from 'next/headers';
|
import { headers } from 'next/headers';
|
||||||
import type { User } from '@/utils/supabase';
|
import type { User, Result } from '@/utils/supabase';
|
||||||
import type { Result } from '.';
|
|
||||||
|
|
||||||
export const signUp = async (
|
export const signUp = async (
|
||||||
formData: FormData,
|
formData: FormData,
|
||||||
@ -58,31 +57,37 @@ export const signIn = async (formData: FormData): Promise<Result<null>> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signInWithMicrosoft = async (): Promise<Result<string>> => {
|
type OAuthReturn = {
|
||||||
|
provider: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signInWithMicrosoft = async (): Promise<Result<OAuthReturn>> => {
|
||||||
const supabase = await createServerClient();
|
const supabase = await createServerClient();
|
||||||
const origin = (await headers()).get('origin');
|
const origin = (await headers()).get('origin');
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: 'azure',
|
provider: 'azure',
|
||||||
options: {
|
options: {
|
||||||
scopes: 'openid, profile email offline_access',
|
scopes: 'openid profile email offline_access',
|
||||||
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) return { success: false, error: error.message };
|
if (error) return { success: false, error: error.message };
|
||||||
return { success: true, data: data.url };
|
return { success: true, data };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signInWithApple = async (): Promise<Result<string>> => {
|
export const signInWithApple = async (): Promise<Result<OAuthReturn>> => {
|
||||||
const supabase = await createServerClient();
|
const supabase = await createServerClient();
|
||||||
const origin = process.env.BASE_URL!;
|
const origin = process.env.BASE_URL!;
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: 'apple',
|
provider: 'apple',
|
||||||
options: {
|
options: {
|
||||||
|
scopes: 'openid profile email offline_access',
|
||||||
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) return { success: false, error: error.message };
|
if (error) return { success: false, error: error.message };
|
||||||
return { success: true, data: data.url };
|
return { success: true, data };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const forgotPassword = async (
|
export const forgotPassword = async (
|
||||||
|
@ -1,22 +1,37 @@
|
|||||||
'use server';
|
'use server';
|
||||||
|
|
||||||
import 'server-only';
|
|
||||||
import { createServerClient, type Profile } from '@/utils/supabase';
|
import { createServerClient, type Profile } from '@/utils/supabase';
|
||||||
import { getUser } from '@/lib/actions';
|
import { getSignedUrl, getUser } from '@/lib/hooks';
|
||||||
import type { Result } from '.';
|
import type { Result } from '.';
|
||||||
|
|
||||||
export const getProfile = async (): Promise<Result<Profile>> => {
|
export const getProfile = async (
|
||||||
|
userId: string | null = null
|
||||||
|
): Promise<Result<Profile>> => {
|
||||||
try {
|
try {
|
||||||
const user = await getUser();
|
if (userId === null) {
|
||||||
if (!user.success || user.data === undefined)
|
const user = await getUser();
|
||||||
throw new Error('User not found');
|
if (!user.success || user.data === undefined)
|
||||||
|
throw new Error('User not found');
|
||||||
|
userId = user.data.id;
|
||||||
|
}
|
||||||
const supabase = await createServerClient();
|
const supabase = await createServerClient();
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', user.data.id)
|
.eq('id', userId)
|
||||||
.single();
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (data.avatar_url) {
|
||||||
|
const avatarUrl = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: data.avatar_url,
|
||||||
|
transform: { width: 128, height: 128 },
|
||||||
|
});
|
||||||
|
if (avatarUrl.success) {
|
||||||
|
data.avatar_url = avatarUrl.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
return { success: true, data: data as Profile };
|
return { success: true, data: data as Profile };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
@ -33,20 +48,22 @@ type updateProfileProps = {
|
|||||||
full_name?: string;
|
full_name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
|
provider?: string
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateProfile = async ({
|
export const updateProfile = async ({
|
||||||
full_name,
|
full_name,
|
||||||
email,
|
email,
|
||||||
avatar_url,
|
avatar_url,
|
||||||
|
provider,
|
||||||
}: updateProfileProps): Promise<Result<Profile>> => {
|
}: updateProfileProps): Promise<Result<Profile>> => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
full_name === undefined &&
|
full_name === undefined &&
|
||||||
email === undefined &&
|
email === undefined &&
|
||||||
avatar_url === undefined
|
avatar_url === undefined &&
|
||||||
)
|
provider === undefined
|
||||||
throw new Error('No profile data provided');
|
) throw new Error('No profile data provided');
|
||||||
|
|
||||||
const userResponse = await getUser();
|
const userResponse = await getUser();
|
||||||
if (!userResponse.success || userResponse.data === undefined)
|
if (!userResponse.success || userResponse.data === undefined)
|
||||||
@ -59,11 +76,21 @@ export const updateProfile = async ({
|
|||||||
...(full_name !== undefined && { full_name }),
|
...(full_name !== undefined && { full_name }),
|
||||||
...(email !== undefined && { email }),
|
...(email !== undefined && { email }),
|
||||||
...(avatar_url !== undefined && { avatar_url }),
|
...(avatar_url !== undefined && { avatar_url }),
|
||||||
|
...(provider !== undefined && { provider }),
|
||||||
})
|
})
|
||||||
.eq('id', userResponse.data.id)
|
.eq('id', userResponse.data.id)
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (data.avatar_url) {
|
||||||
|
const avatarUrl = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: data.avatar_url,
|
||||||
|
transform: { width: 128, height: 128 },
|
||||||
|
});
|
||||||
|
if (avatarUrl.success) data.avatar_url = avatarUrl.data;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: data as Profile,
|
data: data as Profile,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use server';
|
'use server';
|
||||||
import { createServerClient } from '@/utils/supabase';
|
import { createServerClient } from '@/utils/supabase';
|
||||||
import type { Profile, Result } from '@/utils/supabase';
|
import type { Profile, Result } from '@/utils/supabase';
|
||||||
import { getUser, getProfile } from '@/lib/hooks';
|
import { getUser, getProfile, getSignedUrl } from '@/lib/actions';
|
||||||
|
|
||||||
export type UserWithStatus = {
|
export type UserWithStatus = {
|
||||||
id?: string;
|
id?: string;
|
||||||
@ -30,14 +30,12 @@ export const getRecentUsersWithStatuses = async (): Promise<
|
|||||||
|
|
||||||
const { data, error } = (await supabase
|
const { data, error } = (await supabase
|
||||||
.from('statuses')
|
.from('statuses')
|
||||||
.select(
|
.select(`
|
||||||
`
|
|
||||||
user:profiles!user_id(*),
|
user:profiles!user_id(*),
|
||||||
status,
|
status,
|
||||||
created_at,
|
created_at,
|
||||||
updated_by:profiles!updated_by_id(*)
|
updated_by:profiles!updated_by_id(*)
|
||||||
`,
|
`)
|
||||||
)
|
|
||||||
.gte('created_at', oneDayAgo.toISOString())
|
.gte('created_at', oneDayAgo.toISOString())
|
||||||
.order('created_at', { ascending: false })) as {
|
.order('created_at', { ascending: false })) as {
|
||||||
data: UserWithStatus[];
|
data: UserWithStatus[];
|
||||||
@ -47,7 +45,6 @@ export const getRecentUsersWithStatuses = async (): Promise<
|
|||||||
if (error) throw error as Error;
|
if (error) throw error as Error;
|
||||||
if (!data?.length) return { success: true, data: [] };
|
if (!data?.length) return { success: true, data: [] };
|
||||||
|
|
||||||
// 3️⃣ client-side dedupe: keep the first status you see per user
|
|
||||||
const seen = new Set<string>();
|
const seen = new Set<string>();
|
||||||
const filtered = data.filter((row) => {
|
const filtered = data.filter((row) => {
|
||||||
if (seen.has(row.user.id)) return false;
|
if (seen.has(row.user.id)) return false;
|
||||||
@ -55,7 +52,34 @@ export const getRecentUsersWithStatuses = async (): Promise<
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true, data: filtered };
|
const filteredWithAvatars = new Array<UserWithStatus>();
|
||||||
|
|
||||||
|
for (const userWithStatus of filtered) {
|
||||||
|
|
||||||
|
if (userWithStatus.user.avatar_url) {
|
||||||
|
const avatarResponse = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: userWithStatus.user.avatar_url,
|
||||||
|
});
|
||||||
|
if (avatarResponse.success) {
|
||||||
|
userWithStatus.user.avatar_url = avatarResponse.data;
|
||||||
|
} else userWithStatus.user.avatar_url = null;
|
||||||
|
} else userWithStatus.user.avatar_url = null;
|
||||||
|
|
||||||
|
if (userWithStatus.updated_by?.avatar_url) {
|
||||||
|
const updatedByAvatarResponse = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: userWithStatus.updated_by.avatar_url ?? '',
|
||||||
|
});
|
||||||
|
if (updatedByAvatarResponse.success) {
|
||||||
|
userWithStatus.updated_by.avatar_url = updatedByAvatarResponse.data;
|
||||||
|
} else userWithStatus.updated_by.avatar_url = null;
|
||||||
|
} else {
|
||||||
|
if (userWithStatus.updated_by) userWithStatus.updated_by.avatar_url = null;
|
||||||
|
}
|
||||||
|
filteredWithAvatars.push(userWithStatus);
|
||||||
|
}
|
||||||
|
return { success: true, data: filteredWithAvatars };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `Error: ${error as Error}` };
|
return { success: false, error: `Error: ${error as Error}` };
|
||||||
}
|
}
|
||||||
@ -96,17 +120,14 @@ export const updateStatuses = async (
|
|||||||
): Promise<Result<void>> => {
|
): Promise<Result<void>> => {
|
||||||
try {
|
try {
|
||||||
const supabase = await createServerClient();
|
const supabase = await createServerClient();
|
||||||
const userResponse = await getUser();
|
|
||||||
if (!userResponse.success) throw new Error('Not authenticated!');
|
|
||||||
const profileResponse = await getProfile();
|
const profileResponse = await getProfile();
|
||||||
if (!profileResponse.success) throw new Error(profileResponse.error);
|
if (!profileResponse.success) throw new Error('Not authenticated!');
|
||||||
const user = userResponse.data;
|
|
||||||
const userProfile = profileResponse.data;
|
const userProfile = profileResponse.data;
|
||||||
|
|
||||||
const inserts = userIds.map((usersId) => ({
|
const inserts = userIds.map((userId) => ({
|
||||||
user_id: usersId,
|
user_id: userId,
|
||||||
status,
|
status,
|
||||||
updated_by_id: user.id,
|
updated_by_id: userProfile.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data: insertedStatuses, error: insertedStatusesError } =
|
const { data: insertedStatuses, error: insertedStatusesError } =
|
||||||
@ -116,13 +137,9 @@ export const updateStatuses = async (
|
|||||||
if (insertedStatuses) {
|
if (insertedStatuses) {
|
||||||
const broadcastArray = new Array<UserWithStatus>(insertedStatuses.length);
|
const broadcastArray = new Array<UserWithStatus>(insertedStatuses.length);
|
||||||
for (const insertedStatus of insertedStatuses) {
|
for (const insertedStatus of insertedStatuses) {
|
||||||
const { data: profile, error: profileError } = await supabase
|
const profileResponse = await getProfile(insertedStatus.user_id)
|
||||||
.from('profiles')
|
if (!profileResponse.success) throw new Error(profileResponse.error);
|
||||||
.select('*')
|
const profile = profileResponse.data;
|
||||||
.eq('id', insertedStatus.user_id)
|
|
||||||
.single();
|
|
||||||
if (profileError) throw profileError as Error;
|
|
||||||
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
broadcastArray.push({
|
broadcastArray.push({
|
||||||
user: profile,
|
user: profile,
|
||||||
@ -148,21 +165,17 @@ export const updateUserStatus = async (
|
|||||||
): Promise<Result<void>> => {
|
): Promise<Result<void>> => {
|
||||||
try {
|
try {
|
||||||
const supabase = await createServerClient();
|
const supabase = await createServerClient();
|
||||||
const userResponse = await getUser();
|
|
||||||
if (!userResponse.success)
|
|
||||||
throw new Error(`Not authenticated! ${userResponse.error}`);
|
|
||||||
const profileResponse = await getProfile();
|
const profileResponse = await getProfile();
|
||||||
if (!profileResponse.success)
|
if (!profileResponse.success)
|
||||||
throw new Error(`Could not get profile! ${profileResponse.error}`);
|
throw new Error(`Not authenticated! ${profileResponse.error}`);
|
||||||
const user = userResponse.data;
|
|
||||||
const userProfile = profileResponse.data;
|
const userProfile = profileResponse.data;
|
||||||
|
|
||||||
const { data: insertedStatus, error: insertedStatusError } = await supabase
|
const { data: insertedStatus, error: insertedStatusError } = await supabase
|
||||||
.from('statuses')
|
.from('statuses')
|
||||||
.insert({
|
.insert({
|
||||||
user_id: user.id,
|
user_id: userProfile.id,
|
||||||
status,
|
status,
|
||||||
updated_by_id: user.id,
|
updated_by_id: userProfile.id,
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
@ -172,6 +185,7 @@ export const updateUserStatus = async (
|
|||||||
user: userProfile,
|
user: userProfile,
|
||||||
status: insertedStatus.status,
|
status: insertedStatus.status,
|
||||||
created_at: insertedStatus.created_at,
|
created_at: insertedStatus.created_at,
|
||||||
|
updated_by: userProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
await broadcastStatusUpdates([userStatus]);
|
await broadcastStatusUpdates([userStatus]);
|
||||||
@ -220,14 +234,6 @@ export const getUserHistory = async (
|
|||||||
};
|
};
|
||||||
if (statusesError) throw statusesError as Error;
|
if (statusesError) throw statusesError as Error;
|
||||||
|
|
||||||
const { data: profile, error: profileError } = (await supabase
|
|
||||||
.from('profiles')
|
|
||||||
.select('*')
|
|
||||||
.eq('id', userId)
|
|
||||||
.single()) as { data: Profile; error: unknown };
|
|
||||||
if (profileError) throw profileError as Error;
|
|
||||||
if (!profile) throw new Error('User profile not found!');
|
|
||||||
|
|
||||||
const totalCount = count ?? 0;
|
const totalCount = count ?? 0;
|
||||||
const totalPages = Math.ceil(totalCount / perPage);
|
const totalPages = Math.ceil(totalCount / perPage);
|
||||||
|
|
||||||
|
@ -54,7 +54,12 @@ export const signIn = async (formData: FormData): Promise<Result<null>> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signInWithMicrosoft = async (): Promise<Result<string>> => {
|
type OAuthReturn = {
|
||||||
|
provider: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const signInWithMicrosoft = async (): Promise<Result<OAuthReturn>> => {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
@ -65,20 +70,21 @@ export const signInWithMicrosoft = async (): Promise<Result<string>> => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) return { success: false, error: error.message };
|
if (error) return { success: false, error: error.message };
|
||||||
return { success: true, data: data.url };
|
return { success: true, data };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signInWithApple = async (): Promise<Result<string>> => {
|
export const signInWithApple = async (): Promise<Result<OAuthReturn>> => {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
const origin = process.env.NEXT_PUBLIC_SITE_URL!;
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: 'apple',
|
provider: 'apple',
|
||||||
options: {
|
options: {
|
||||||
|
scopes: 'openid profile email offline_access',
|
||||||
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
redirectTo: `${origin}/auth/callback?redirect_to=/auth/success`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (error) return { success: false, error: error.message };
|
if (error) return { success: false, error: error.message };
|
||||||
return { success: true, data: data.url };
|
return { success: true, data };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const forgotPassword = async (
|
export const forgotPassword = async (
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { createClient, type Profile } from '@/utils/supabase';
|
import { createClient, type Profile } from '@/utils/supabase';
|
||||||
import { getUser } from '@/lib/hooks';
|
import { getSignedUrl, getUser } from '@/lib/hooks';
|
||||||
import type { Result } from '.';
|
import type { Result } from '.';
|
||||||
|
|
||||||
export const getProfile = async (): Promise<Result<Profile>> => {
|
export const getProfile = async (
|
||||||
|
userId: string | null = null
|
||||||
|
): Promise<Result<Profile>> => {
|
||||||
try {
|
try {
|
||||||
const user = await getUser();
|
if (userId === null) {
|
||||||
if (!user.success || user.data === undefined)
|
const user = await getUser();
|
||||||
throw new Error('User not found');
|
if (!user.success || user.data === undefined)
|
||||||
|
throw new Error('User not found');
|
||||||
|
userId = user.data.id;
|
||||||
|
}
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('id', user.data.id)
|
.eq('id', userId)
|
||||||
.single();
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (data.avatar_url) {
|
||||||
|
const avatarUrl = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: data.avatar_url,
|
||||||
|
transform: { width: 128, height: 128 },
|
||||||
|
});
|
||||||
|
if (avatarUrl.success) {
|
||||||
|
data.avatar_url = avatarUrl.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
return { success: true, data: data as Profile };
|
return { success: true, data: data as Profile };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
@ -32,20 +48,22 @@ type updateProfileProps = {
|
|||||||
full_name?: string;
|
full_name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
avatar_url?: string;
|
avatar_url?: string;
|
||||||
|
provider?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const updateProfile = async ({
|
export const updateProfile = async ({
|
||||||
full_name,
|
full_name,
|
||||||
email,
|
email,
|
||||||
avatar_url,
|
avatar_url,
|
||||||
|
provider,
|
||||||
}: updateProfileProps): Promise<Result<Profile>> => {
|
}: updateProfileProps): Promise<Result<Profile>> => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
full_name === undefined &&
|
full_name === undefined &&
|
||||||
email === undefined &&
|
email === undefined &&
|
||||||
avatar_url === undefined
|
avatar_url === undefined &&
|
||||||
)
|
provider === undefined
|
||||||
throw new Error('No profile data provided');
|
) throw new Error('No profile data provided');
|
||||||
|
|
||||||
const userResponse = await getUser();
|
const userResponse = await getUser();
|
||||||
if (!userResponse.success || userResponse.data === undefined)
|
if (!userResponse.success || userResponse.data === undefined)
|
||||||
@ -58,11 +76,21 @@ export const updateProfile = async ({
|
|||||||
...(full_name !== undefined && { full_name }),
|
...(full_name !== undefined && { full_name }),
|
||||||
...(email !== undefined && { email }),
|
...(email !== undefined && { email }),
|
||||||
...(avatar_url !== undefined && { avatar_url }),
|
...(avatar_url !== undefined && { avatar_url }),
|
||||||
|
...(provider !== undefined && { provider }),
|
||||||
})
|
})
|
||||||
.eq('id', userResponse.data.id)
|
.eq('id', userResponse.data.id)
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
|
|
||||||
|
if (data.avatar_url) {
|
||||||
|
const avatarUrl = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: data.avatar_url,
|
||||||
|
transform: { width: 128, height: 128 },
|
||||||
|
});
|
||||||
|
if (avatarUrl.success) data.avatar_url = avatarUrl.data;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: data as Profile,
|
data: data as Profile,
|
||||||
|
@ -7,7 +7,6 @@ export type UserWithStatus = {
|
|||||||
id?: string;
|
id?: string;
|
||||||
user: Profile;
|
user: Profile;
|
||||||
status: string;
|
status: string;
|
||||||
avatar_url?: string;
|
|
||||||
created_at: string;
|
created_at: string;
|
||||||
updated_by?: Profile;
|
updated_by?: Profile;
|
||||||
};
|
};
|
||||||
@ -54,20 +53,33 @@ export const getRecentUsersWithStatuses = async (): Promise<
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filteredWithAvatars: UserWithStatus[] = filtered;
|
const filteredWithAvatars = new Array<UserWithStatus>();
|
||||||
|
|
||||||
for (let userWithStatus of filteredWithAvatars) {
|
for (const userWithStatus of filtered) {
|
||||||
if (!userWithStatus.user.avatar_url) continue;
|
|
||||||
const avatarResponse = await getSignedUrl({
|
if (userWithStatus.user.avatar_url) {
|
||||||
bucket: 'avatars',
|
const avatarResponse = await getSignedUrl({
|
||||||
url: userWithStatus.user.avatar_url,
|
bucket: 'avatars',
|
||||||
transform: { width: 128, height: 128 },
|
url: userWithStatus.user.avatar_url,
|
||||||
});
|
});
|
||||||
if (!avatarResponse.success) continue;
|
if (avatarResponse.success) {
|
||||||
else userWithStatus = { ...userWithStatus, avatar_url: avatarResponse.data };
|
userWithStatus.user.avatar_url = avatarResponse.data;
|
||||||
|
} else userWithStatus.user.avatar_url = null;
|
||||||
|
} else userWithStatus.user.avatar_url = null;
|
||||||
|
|
||||||
|
if (userWithStatus.updated_by?.avatar_url) {
|
||||||
|
const updatedByAvatarResponse = await getSignedUrl({
|
||||||
|
bucket: 'avatars',
|
||||||
|
url: userWithStatus.updated_by.avatar_url ?? '',
|
||||||
|
});
|
||||||
|
if (updatedByAvatarResponse.success) {
|
||||||
|
userWithStatus.updated_by.avatar_url = updatedByAvatarResponse.data;
|
||||||
|
} else userWithStatus.updated_by.avatar_url = null;
|
||||||
|
} else {
|
||||||
|
if (userWithStatus.updated_by) userWithStatus.updated_by.avatar_url = null;
|
||||||
|
}
|
||||||
|
filteredWithAvatars.push(userWithStatus);
|
||||||
}
|
}
|
||||||
console.log('filteredWithAvatars', filteredWithAvatars);
|
|
||||||
|
|
||||||
return { success: true, data: filteredWithAvatars };
|
return { success: true, data: filteredWithAvatars };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { success: false, error: `Error: ${error as Error}` };
|
return { success: false, error: `Error: ${error as Error}` };
|
||||||
@ -109,17 +121,14 @@ export const updateStatuses = async (
|
|||||||
): Promise<Result<void>> => {
|
): Promise<Result<void>> => {
|
||||||
try {
|
try {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const userResponse = await getUser();
|
|
||||||
if (!userResponse.success) throw new Error('Not authenticated!');
|
|
||||||
const profileResponse = await getProfile();
|
const profileResponse = await getProfile();
|
||||||
if (!profileResponse.success) throw new Error(profileResponse.error);
|
if (!profileResponse.success) throw new Error('Not authenticated!');
|
||||||
const user = userResponse.data;
|
|
||||||
const userProfile = profileResponse.data;
|
const userProfile = profileResponse.data;
|
||||||
|
|
||||||
const inserts = userIds.map((usersId) => ({
|
const inserts = userIds.map((userId) => ({
|
||||||
user_id: usersId,
|
user_id: userId,
|
||||||
status,
|
status,
|
||||||
updated_by_id: user.id,
|
updated_by_id: userProfile.id,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data: insertedStatuses, error: insertedStatusesError } =
|
const { data: insertedStatuses, error: insertedStatusesError } =
|
||||||
@ -129,13 +138,9 @@ export const updateStatuses = async (
|
|||||||
if (insertedStatuses) {
|
if (insertedStatuses) {
|
||||||
const broadcastArray = new Array<UserWithStatus>(insertedStatuses.length);
|
const broadcastArray = new Array<UserWithStatus>(insertedStatuses.length);
|
||||||
for (const insertedStatus of insertedStatuses) {
|
for (const insertedStatus of insertedStatuses) {
|
||||||
const { data: profile, error: profileError } = await supabase
|
const profileResponse = await getProfile(insertedStatus.user_id)
|
||||||
.from('profiles')
|
if (!profileResponse.success) throw new Error(profileResponse.error);
|
||||||
.select('*')
|
const profile = profileResponse.data;
|
||||||
.eq('id', insertedStatus.user_id)
|
|
||||||
.single();
|
|
||||||
if (profileError) throw profileError as Error;
|
|
||||||
|
|
||||||
if (profile) {
|
if (profile) {
|
||||||
broadcastArray.push({
|
broadcastArray.push({
|
||||||
user: profile,
|
user: profile,
|
||||||
@ -161,21 +166,17 @@ export const updateUserStatus = async (
|
|||||||
): Promise<Result<void>> => {
|
): Promise<Result<void>> => {
|
||||||
try {
|
try {
|
||||||
const supabase = createClient();
|
const supabase = createClient();
|
||||||
const userResponse = await getUser();
|
|
||||||
if (!userResponse.success)
|
|
||||||
throw new Error(`Not authenticated! ${userResponse.error}`);
|
|
||||||
const profileResponse = await getProfile();
|
const profileResponse = await getProfile();
|
||||||
if (!profileResponse.success)
|
if (!profileResponse.success)
|
||||||
throw new Error(`Could not get profile! ${profileResponse.error}`);
|
throw new Error(`Not authenticated! ${profileResponse.error}`);
|
||||||
const user = userResponse.data;
|
|
||||||
const userProfile = profileResponse.data;
|
const userProfile = profileResponse.data;
|
||||||
|
|
||||||
const { data: insertedStatus, error: insertedStatusError } = await supabase
|
const { data: insertedStatus, error: insertedStatusError } = await supabase
|
||||||
.from('statuses')
|
.from('statuses')
|
||||||
.insert({
|
.insert({
|
||||||
user_id: user.id,
|
user_id: userProfile.id,
|
||||||
status,
|
status,
|
||||||
updated_by_id: user.id,
|
updated_by_id: userProfile.id,
|
||||||
})
|
})
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
@ -234,14 +235,6 @@ export const getUserHistory = async (
|
|||||||
};
|
};
|
||||||
if (statusesError) throw statusesError as Error;
|
if (statusesError) throw statusesError as Error;
|
||||||
|
|
||||||
const { data: profile, error: profileError } = (await supabase
|
|
||||||
.from('profiles')
|
|
||||||
.select('*')
|
|
||||||
.eq('id', userId)
|
|
||||||
.single()) as { data: Profile; error: unknown };
|
|
||||||
if (profileError) throw profileError as Error;
|
|
||||||
if (!profile) throw new Error('User profile not found!');
|
|
||||||
|
|
||||||
const totalCount = count ?? 0;
|
const totalCount = count ?? 0;
|
||||||
const totalPages = Math.ceil(totalCount / perPage);
|
const totalPages = Math.ceil(totalCount / perPage);
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user