Skip to content

Instantly share code, notes, and snippets.

@simon-marcus
Created January 5, 2024 19:27
Show Gist options
  • Save simon-marcus/76c18127f03846050429d56950a3f113 to your computer and use it in GitHub Desktop.
Save simon-marcus/76c18127f03846050429d56950a3f113 to your computer and use it in GitHub Desktop.
'use client';
import { Card, Form, Input, Button, message, ConfigProvider, Upload } from 'antd';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import { sanitize } from "isomorphic-dompurify";import { useSupabase } from '../supabase-provider';
import mytheme from '../theme/themeConfig';
import { User } from '@supabase/supabase-js';
import { useImageUploader } from '@/hooks/useImageUploader';
import ImgCrop from 'antd-img-crop';
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
export default function ProfileTab({ user }: { user: User }) {
const { supabase, session } = useSupabase();
const userId = session?.user.id || {};
const [saving, setSaving] = useState(false);
const [canSave, setCanSave ] = useState(true);
const [form] = Form.useForm();
const [loading, setLoading] = useState(false);
const [newAvatarUrl, setNewAvatarUrl] = useState<string>();
const router = useRouter();
const [messageApi, contextHolder] = message.useMessage();
const key = 'updatable';
const { handleUploadImage, createBase64Url, beforeUpload } = useImageUploader(userId as string);
const avatarUrl = session?.user.user_metadata.avatar_url;
const uploadButton = (
<div style={{ borderRadius: '100%' }}>
{loading ? <LoadingOutlined /> : <PlusOutlined />}
<div>Upload</div>
</div>
);
const openSaveMessage = () => {
messageApi.open({
key,
type: 'loading',
content: 'Saving your changes...',
});
};
const onFinish = async (values: any) => {
openSaveMessage();
setSaving(true);
if (session && session.user) {
if (!avatarUrl && !newAvatarUrl ){
return message.error('Please upload an icon image.');
}
let avatar_url: string | undefined;
const getImage = new Promise<string | undefined>(async (resolve, reject) => {
if (newAvatarUrl) {
avatar_url = await handleUploadImage(newAvatarUrl as string, user.id, 'avatar_url', 500, 500);
if (avatar_url == undefined) {
message.error('Error uploading image');
reject(undefined);
}
else {
resolve(avatar_url);
}
} else {
resolve(undefined);}
});
const updatedUser = await supabase.auth.updateUser({
data: {
full_name: sanitize(values.full_name).trim(),
website: sanitize(values.website).trim(),
avatar_url: newAvatarUrl ? await getImage : values.avatar_url,
payment_link: sanitize(values.payment_link).trim(),
},
});
if (updatedUser.error) {
return message.error('Error updating profile. Please try again.');
}
message.success('Profile updated successfully');
const { data: { session }, error } = await supabase.auth.refreshSession(); // Refresh session to ensure user metadata is up to date with current user
router.push('/myapps');
}
};
return (user ? (
<ConfigProvider theme={mytheme}>
{contextHolder}
<Form form={form} onFinish={onFinish} layout='vertical'
initialValues={{
email: user.email,
full_name: user.user_metadata.full_name,
avatar_url: user.user_metadata.avatar_url,
website: user.user_metadata.website,
payment_link: user.user_metadata.payment_link,
}}
>
<Card title="Profile" >
<Form.Item label="Email" name="email">
<Input
disabled
type="email"
name="email"
placeholder="Your email address"
/>
</Form.Item>
<Form.Item label="Name"
name="full_name"
id="full_name"
required={true}
hasFeedback
tooltip="Your full name, or the name you want to be credited for the app"
rules={[
{ required: true, message: 'Please complete this field.' },
{
type: 'string',
min: 3,
message: 'Please use at least 3 characters.',
},
{
type: 'string',
max: 70,
message: 'Please use at most 70 characters.',
},
{
pattern: /^[a-zA-Z\s'-.]*$/,
message: 'Please use only letters, hyphens, apostrophes and periods in your name.',
}
]}>
<Input
type="text"
name="full_name"
id="full_name"
placeholder="Your preferred name"
/>
</Form.Item>
<Form.Item label="Website"
name="website"
id="website"
tooltip="Your personal or business website or portfolio"
rules={[
{
type: 'url',
message: 'Please enter a valid URL.',
}
]}>
<Input
type="url"
name="website"
id="website"
placeholder="https://yourwebsite.com"
/>
</Form.Item>
<Form.Item
required
label="Icon"
name="avatar_url"
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) {
return e;
}
return e && e.fileList;
}}
>
<ImgCrop
quality={0.8}
rotationSlider
showReset
cropShape="round"
>
<Upload name="avatar_url" listType="picture-circle" multiple={false} maxCount={1} showUploadList={false} style={{ borderRadius: '100%' }}
beforeUpload={beforeUpload}
customRequest={async ({ file }) => {
try {
const imageUrl = await createBase64Url([file as File])
if (imageUrl) {
setNewAvatarUrl(imageUrl);
} else {
return new Error('Error uploading image. Please try again.');
}
} catch (error) {
console.error(error);
return new Error('Error uploading image. Please try again.');
}
}}
>
{newAvatarUrl ? <img src={newAvatarUrl} alt="avatar" style={{ width: '150px', borderRadius: '100%' }} /> : avatarUrl ? <img src={avatarUrl} alt="avatar" style={{ width: '150px', borderRadius: '100%' }} /> : uploadButton}
</Upload>
</ImgCrop>
</Form.Item>
<Form.Item
label="Payment Link"
name="payment_link"
tooltip="Link to PayPal, Venmo, CashApp, or other payment service. Will appear on your app page for users to send you tips."
rules={[
{
type: 'url',
message: 'Please enter a valid URL.',
}
]}>
<Input
type="url"
name="payment_link"
id="payment_link"
placeholder="https://paypal.me/yourname"
/>
</Form.Item>
</Card >
<div className="flex justify-between items-center my-3">
<Button size='large' type='text' onClick={() => router.push('/myapps')}>
Cancel
</Button>
<Button size='large' type='primary' disabled={!canSave} htmlType="submit">
Save
</Button>
</div>
</Form>
</ConfigProvider >
) : null);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment