Skip to content

Instantly share code, notes, and snippets.

@adrianhajdin
Last active April 6, 2025 22:14
Show Gist options
  • Save adrianhajdin/c9e83f0fb1dfcf238dae0cc68a90ba82 to your computer and use it in GitHub Desktop.
Save adrianhajdin/c9e83f0fb1dfcf238dae0cc68a90ba82 to your computer and use it in GitHub Desktop.
Build and Deploy a Full Stack MERN Dashboard App With CRUD, Auth, and Charts Using Refine
import { ApexOptions } from 'apexcharts';
export const TotalRevenueSeries = [
{
name: 'Last Month',
data: [183, 124, 115, 85, 143, 143, 96],
},
{
name: 'Running Month',
data: [95, 84, 72, 44, 108, 108, 47],
},
];
export const TotalRevenueOptions: ApexOptions = {
chart: {
type: 'bar',
toolbar: {
show: false,
},
},
colors: ['#475BE8', '#CFC8FF'],
plotOptions: {
bar: {
borderRadius: 4,
horizontal: false,
columnWidth: '55%',
},
},
dataLabels: {
enabled: false,
},
grid: {
show: false,
},
stroke: {
colors: ['transparent'],
width: 4,
},
xaxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'],
},
yaxis: {
title: {
text: '$ (thousands)',
},
},
fill: {
opacity: 1,
},
legend: {
position: 'top',
horizontalAlign: 'right',
},
tooltip: {
y: {
formatter(val: number) {
return `$ ${val} thousands`;
},
},
},
};
import { Email, Phone, Place } from '@mui/icons-material';
import { Box, Stack, Typography } from '@pankod/refine-mui';
import { ProfileProps, PropertyProps } from 'interfaces/common';
import PropertyCard from './PropertyCard';
function checkImage(url: any) {
let img = new Image();
img.src = url;
return img.width !== 0 && img.height !== 0;
}
const Profile = ({ type, name, avatar, email, properties }: ProfileProps) => (
<Box>
<Typography fontSize={25} fontWeight={700} color="#11142D">{type} Profile</Typography>
<Box
mt="20px"
borderRadius="15px"
padding="20px"
bgcolor="#FCFCFC"
>
<Box
sx={{
display: 'flex',
flexDirection: { xs: 'column', md: 'row' },
gap: 2.5,
}}
>
<img
src="https://images.unsplash.com/photo-1618005198919-d3d4b5a92ead?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1374&q=80"
width={340}
height={320}
alt="abstract"
className="my_profile-bg"
/>
<Box
flex={1}
sx={{ marginTop: { md: '58px' }, marginLeft: { xs: '20px', md: '0px' } }}
>
<Box flex={1} display="flex" flexDirection={{ xs: 'column', md: 'row' }} gap="20px">
<img
src={checkImage(avatar) ? avatar : "https://upload.wikimedia.org/wikipedia/commons/thumb/5/59/User-avatar.svg/2048px-User-avatar.svg.png"}
width={78}
height={78}
alt="user_profile"
className="my_profile_user-img"
/>
<Box flex={1} display="flex" flexDirection="column" justifyContent="space-between" gap="30px">
<Stack direction="column">
<Typography fontSize={22} fontWeight={600} color="#11142D">{name}</Typography>
<Typography fontSize={16} color="#808191">Realestate Agent</Typography>
</Stack>
<Stack direction="column" gap="30px">
<Stack gap="15px">
<Typography fontSize={14} fontWeight={500} color="#808191">Address</Typography>
<Box display="flex" flexDirection="row" alignItems="center" gap="10px">
<Place sx={{ color: '#11142D' }} />
<Typography fontSize={14} color="#11142D">4517 Washington Ave. Manchaster, Kentucky 39495</Typography>
</Box>
</Stack>
<Stack direction="row" flexWrap="wrap" gap="20px" pb={4}>
<Stack flex={1} gap="15px">
<Typography fontSize={14} fontWeight={500} color="#808191">Phone Number</Typography>
<Box display="flex" flexDirection="row" alignItems="center" gap="10px">
<Phone sx={{ color: '#11142D' }} />
<Typography fontSize={14} color="#11142D" noWrap>+0123 456 7890</Typography>
</Box>
</Stack>
<Stack flex={1} gap="15px">
<Typography fontSize={14} fontWeight={500} color="#808191">Email</Typography>
<Box display="flex" flexDirection="row" alignItems="center" gap="10px">
<Email sx={{ color: '#11142D' }} />
<Typography fontSize={14} color="#11142D">{email}</Typography>
</Box>
</Stack>
</Stack>
</Stack>
</Box>
</Box>
</Box>
</Box>
</Box>
{properties.length > 0 && (
<Box
mt={2.5}
borderRadius="15px"
padding="20px"
bgcolor="#FCFCFC"
>
<Typography fontSize={18} fontWeight={600} color="#11142D">{type} Properties</Typography>
<Box
mt={2.5}
sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 2.5,
}}
>
{properties?.map((property: PropertyProps) => (
<PropertyCard key={property._id} id={property._id}
title={property.title}
location={property.location}
price={property.price}
photo={property.photo}
/>
))}
</Box>
</Box>
)}
</Box>
);
export default Profile;
// common
import Profile from './common/Profile';
import PropertyCard from './common/PropertyCard';
import CustomButton from './common/CustomButton';
// charts
import PieChart from './charts/PieChart';
import PropertyReferrals from './charts/PropertyReferrals';
import TotalRevenue from './charts/TotalRevenue';
// agent
import AgentCard from './agent/AgentCard';
// home
import TopAgent from './home/TopAgent';
export {
Profile,
PropertyCard,
CustomButton,
PieChart,
PropertyReferrals,
TotalRevenue,
AgentCard,
TopAgent,
};
export const propertyReferralsInfo = [
{
title: 'Social Media',
percentage: 64,
color: '#6C5DD3',
},
{
title: 'Marketplace',
percentage: 40,
color: '#7FBA7A',
},
{
title: 'Websites',
percentage: 50,
color: '#FFCE73',
},
{
title: 'Digital Ads',
percentage: 80,
color: '#FFA2C0',
},
{
title: 'Others',
percentage: 15,
color: '#F45252',
},
];
* {
font-family: 'Manrope', sans-serif !important;
}
a {
text-decoration: none !important;
}
.my_profile-bg {
width: 340px;
border-top-left-radius: 15px;
border-bottom-left-radius: 15px;
}
.my_profile_user-img {
border-radius: 100%;
margin-left: -64px;
}
.property_details-img {
width: 100%;
}
@media screen and (max-width: 900px) {
.my_profile-bg {
width: 100%;
border-radius: 15px;
}
.my_profile_user-img {
margin-left: 0px;
margin-top: -64px;
}
.property_details-img {
width: 100%;
height: auto;
}
}
import { useState } from 'react';
import { useGetIdentity } from '@pankod/refine-core';
import { FieldValues, useForm } from '@pankod/refine-react-hook-form';
import Form from 'components/common/Form';
const CreateProperty = () => {
const { data: user } = useGetIdentity();
const [propertyImage, setPropertyImage] = useState({ name: '', url: '' });
const { refineCore: { onFinish, formLoading }, register, handleSubmit } = useForm();
const handleImageChange = (file: File) => {
const reader = (readFile: File) => new Promise<string>((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader.result as string);
fileReader.readAsDataURL(readFile);
});
reader(file).then((result: string) => setPropertyImage({ name: file?.name, url: result }));
};
const onFinishHandler = async (data: FieldValues) => {
if(!propertyImage.name) return alert('Please select an image');
await onFinish({ ...data, photo: propertyImage.url, email: user.email })
};
return (
<Form
type="Create"
register={register}
onFinish={onFinish}
formLoading={formLoading}
handleSubmit={handleSubmit}
handleImageChange={handleImageChange}
onFinishHandler={onFinishHandler}
propertyImage={propertyImage}
/>
)
}
export default CreateProperty
import { useState } from 'react';
import { useGetIdentity } from '@pankod/refine-core';
import { FieldValues, useForm } from '@pankod/refine-react-hook-form';
import Form from 'components/common/Form';
const CreateProperty = () => {
const { data: user } = useGetIdentity();
const [propertyImage, setPropertyImage] = useState({ name: '', url: '' });
const { refineCore: { onFinish, formLoading }, register, handleSubmit } = useForm();
const handleImageChange = (file: File) => {
const reader = (readFile: File) => new Promise<string>((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = () => resolve(fileReader.result as string);
fileReader.readAsDataURL(readFile);
});
reader(file).then((result: string) => setPropertyImage({ name: file?.name, url: result }));
};
const onFinishHandler = async (data: FieldValues) => {
if (!propertyImage.name) return alert('Please upload a property image');
await onFinish({ ...data, photo: propertyImage.url, email: user.email });
};
return (
<Form
type="Edit"
register={register}
onFinish={onFinish}
formLoading={formLoading}
handleSubmit={handleSubmit}
handleImageChange={handleImageChange}
onFinishHandler={onFinishHandler}
propertyImage={propertyImage}
/>
);
};
export default CreateProperty;
import AgentProfile from './agent-profile';
import Agents from './agent';
import AllProperties from './all-properties';
import CreateProperty from './create-property';
import Home from './home';
import { Login } from './login';
import MyProfile from './my-profile';
import PropertyDetails from './property-details';
import EditProperty from './edit-property';
export {
AgentProfile,
Agents,
AllProperties,
CreateProperty,
Home,
Login,
MyProfile,
PropertyDetails,
EditProperty,
};
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@200;300;400;500;600;700;800&display=swap" rel="stylesheet">
/* eslint-disable no-plusplus */
import { FormValues } from 'interfaces/property';
export const validateForm = (formValues: FormValues) => {
const errors: { message: string } = { message: '' };
let hasError = false;
Object.keys(formValues).forEach((key) => {
switch (key) {
case 'title':
if (!formValues.title) {
errors.message = 'Title is required';
hasError = true;
}
break;
case 'description':
if (!formValues.description) {
errors.message = 'Description is required';
hasError = true;
}
break;
case 'propertyType':
if (!formValues.propertyType) {
errors.message = 'Property type is required';
hasError = true;
}
break;
case 'location':
if (!formValues.location) {
errors.message = 'Location is required';
hasError = true;
}
break;
case 'price':
if (!formValues.price) {
errors.message = 'Price is required';
hasError = true;
}
break;
default:
hasError = false;
}
});
return { hasError, errors };
};
export const hasChanged = (initialValues: FormValues, currentValues: FormValues) => {
const initialValuesArray = Object.values(initialValues);
const currentValuesArray = Object.values(currentValues);
for (let i = 0; i < initialValuesArray.length; i++) {
if (initialValuesArray[i] !== currentValuesArray[i]) {
return true;
}
}
return false;
};
@ayushindapure
Copy link

import { useEffect, useRef } from "react";
import { useLogin } from "@refinedev/core";
import Container from "@mui/material/Container";
import Box from "@mui/material/Box";
// import { yariga } from "../assets";

import { CredentialResponse } from "../interfaces/google";

export const Login: React.FC = () => {
const { mutate: login } = useLogin({
v3LegacyAuthProviderCompatible: true,
});

const GoogleButton = (): JSX.Element => {
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
      if (
          typeof window === "undefined" ||
          !window.google ||
          !divRef.current
      ) {
          return;
      }

      try {
          console.log("Initializing Google Button");
          window.google.accounts.id.initialize({
              ux_mode: "popup",
              client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
              callback: async (res: CredentialResponse) => {
                  console.log("Google callback response:", res);
                  if (res.credential) {
                      console.log("Login successful, calling login function.");
                      login(res);
                  } else {
                      console.log("No credential received.");
                  }
              },
          });
          window.google.accounts.id.renderButton(divRef.current, {
              theme: "filled_blue",
              size: "medium",
              type: "standard",
          });
      } catch (error) {
          console.log("Error rendering Google Button:", error);
      }
  }, [login]);

  return <div ref={divRef} />;

};

return (
    <Box component="div" sx={{ backgroundColor: "white" }}>
        <Container
            component="main"
            maxWidth="xs"
            sx={{
                display: "flex",
                flexDirection: "column",
                justifyContent: "center",
                height: "100vh",
            }}
        >
            <Box
                sx={{
                    display: "flex",
                    justifyContent: "center",
                    flexDirection: "column",
                    alignItems: "center",
                }}
            >
                <div>
                    {/* <img src={yariga} alt="Yariga Logo" /> */}
                </div>
                <Box mt={4}>
                    <GoogleButton />
                </Box>
            </Box>
        </Container>
    </Box>
);

};

after successfull login the page is redirecting to the same login page where my Google button in present why so? Please help

@Atahhabibi
Copy link

this any one know how to change in light mode and i was working on the project once logout and then back in it turn into dark mode
dar

@hakkuuuu
Copy link

import { useEffect, useRef } from "react"; import { useLogin } from "@refinedev/core"; import Container from "@mui/material/Container"; import Box from "@mui/material/Box"; // import { yariga } from "../assets";

import { CredentialResponse } from "../interfaces/google";

export const Login: React.FC = () => { const { mutate: login } = useLogin({ v3LegacyAuthProviderCompatible: true, });

const GoogleButton = (): JSX.Element => {
  const divRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
      if (
          typeof window === "undefined" ||
          !window.google ||
          !divRef.current
      ) {
          return;
      }

      try {
          console.log("Initializing Google Button");
          window.google.accounts.id.initialize({
              ux_mode: "popup",
              client_id: import.meta.env.VITE_GOOGLE_CLIENT_ID,
              callback: async (res: CredentialResponse) => {
                  console.log("Google callback response:", res);
                  if (res.credential) {
                      console.log("Login successful, calling login function.");
                      login(res);
                  } else {
                      console.log("No credential received.");
                  }
              },
          });
          window.google.accounts.id.renderButton(divRef.current, {
              theme: "filled_blue",
              size: "medium",
              type: "standard",
          });
      } catch (error) {
          console.log("Error rendering Google Button:", error);
      }
  }, [login]);

  return <div ref={divRef} />;

};

return (
    <Box component="div" sx={{ backgroundColor: "white" }}>
        <Container
            component="main"
            maxWidth="xs"
            sx={{
                display: "flex",
                flexDirection: "column",
                justifyContent: "center",
                height: "100vh",
            }}
        >
            <Box
                sx={{
                    display: "flex",
                    justifyContent: "center",
                    flexDirection: "column",
                    alignItems: "center",
                }}
            >
                <div>
                    {/* <img src={yariga} alt="Yariga Logo" /> */}
                </div>
                <Box mt={4}>
                    <GoogleButton />
                </Box>
            </Box>
        </Container>
    </Box>
);

};

after successfull login the page is redirecting to the same login page where my Google button in present why so? Please help

I got the same issue, have you resolve it yet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment