સુપરબેસ, રિએક્ટ, એસ્ટ્રો, અને ક્લાઉડફ્લેર સાથે એક સ્લિક બ્લોગ બાંધવું – ભાગ 3: તમારું એડમિન ઇન્ટરફેસ સેટઅપ કરવું.

/* by Tirth Bodawala - August 17, 2024 */

અમારા બ્લોગ-બાંધવાના સિરીઝના ભાગ 3 માં આપનું સ્વાગત છે! હવે અમે સુપરબેસ સાથે એક મજબૂત પાયો મૂક્યો છે અને તેને ફંક્શન્સ, ટ્રિગર્સ, અને RLS સાથે સુરક્ષિત કર્યો છે, હવે બધું એકસાથે લાવવાનો સમય છે એડમિન ઇન્ટરફેસ સેટઅપ કરીને. અહીં તમે તમારી સામગ્રીનું મેનેજ કરો, અપલોડ્સ હેન્ડલ કરો, અને એક યૂઝર-ફ્રેન્ડલી ડેશબોર્ડથી સંપૂર્ણ બ્લોગને કંટ્રોલ કરશો. અમે એસ્ટ્રો, રિએક્ટ, અને ટેઇલવિન્ડ CSS નો ઉપયોગ કરીશું એક સ્લીક, આધુનિક એડમિન પેનલ બનાવવા માટે જે સુપરબેસ સાથે સરળતાથી કનેક્ટ થાય.

ટેબલ ઓફ કન્ટેન્ટ્સ

  1. એસ્ટ્રો અને રિએક્ટ સાથે પ્રોજેક્ટ સેટઅપ કરવો.
    • એસ્ટ્રો પ્રોજેક્ટ ઇનિશિયલાઇઝ કરવું.
    • રિએક્ટ, ટેઇલવિન્ડ, અને ક્લાઉડફ્લેર ઈન્ટિગ્રેશન ઉમેરવું.
    • જરૂરી ડીપેન્ડન્સીઓ ઇન્સ્ટોલ કરવી.
  2. એન્વાયર્નમેન્ટ વેરિયબલ્સ કન્ફિગર કરવું.
  3. સુપરબેસ યુટિલિટીઝ બનાવવી.
    • સુપરબેસ ક્લાયન્ટ સેટઅપ.
    • ઓથ પ્રોવાઇડર.
    • ડેટા પ્રોવાઇડર.
  4. એડમિન ઇન્ટરફેસ બનાવવું.
    • એડમિન એપ બનાવવી.
    • પોસ્ટ બનાવવાની અને એડિટ કરવાની ફોર્મ્સ સેટઅપ કરવી.
    • પોસ્ટ્સ પ્રદર્શિત અને મેનેજ કરવી.

1. એસ્ટ્રો અને રિએક્ટ સાથે પ્રોજેક્ટ સેટઅપ કરવો.

ચાલો એસ્ટ્રો પ્રોજેક્ટ સેટઅપ કરીને, અને તેને રિએક્ટ, ટેઇલવિન્ડ, અને ક્લાઉડફ્લેર સાથે ઇન્ટિગ્રેટ કરીને શરૂઆત કરીએ.

એસ્ટ્રો પ્રોજેક્ટ ઇનિશિયલાઇઝ કરવું.

નવો એસ્ટ્રો પ્રોજેક્ટ બનાવીને શરૂઆત કરો:

npm create astro@latest

તમારા પ્રોજેક્ટને સેટઅપ કરવા માટે પ્રોમ્પ્ટ્સને અનુસરો. વસ્તુઓને સરળ રાખવા માટે મિનિમલ ટેમ્પલેટ પસંદ કરો.

રિએક્ટ, ટેઇલવિન્ડ, અને ક્લાઉડફ્લેર ઈન્ટિગ્રેશન ઉમેરવું.

આગળ, આપણે અમારા એસ્ટ્રો પ્રોજેક્ટમાં રિએક્ટ, ટેલવિન્ડ CSS, અને ક્લાઉડફ્લેર વર્કર્સ સપોર્ટ ઉમેરશું:

npx astro add cloudflare react tailwind

આ આદેશ જરૂરી રૂપરેખા ફાઇલો અને આર્થિકાઓને તૈયાર કરશે જેથી કરીને રિએક્ટ, ટેલવિન્ડ, અને ક્લાઉડફ્લેર સાથે એસ્ટ્રો ચલાવી શકાય.

જરૂરી ડીપેન્ડન્સીઓ ઇન્સ્ટોલ કરવી.

હવે, અમારા એડમિન ઈન્ટરફેસને બનાવવા માટે જરૂરી બાકીની આર્થિકાઓ સ્થાપિત કરો:

npm i @supabase/supabase-js ra-input-rich-text ra-supabase react-admin tailwindcss

આ પેકેજો અમને સમૃદ્ધ લખાણ સંપાદકો બનાવવામાં, સુપાબેસ સંકલનને મેનેજ કરવામાં, અને રિએક્ટ એડમિનનો ઉપયોગ કરીને એક પ્રતિસાદી એડમિન ઈન્ટરફેસ બનાવવા દેવામાં મદદ કરશે.


2. પર્યાવરણ ચલને કોન્ફિગર કરવી

સુપાબેસ તમારા પ્રોજેક્ટ સાથે કનેક્ટ કરવા માટે પર્યાવરણ ચલ પર આધાર રાખે છે. ચાલો તેને સેટ કરીએ.

src/.env.d.ts ફાઇલ અપડેટ કરી રહ્યા છે


તમારા પર્યાવરણ ચલ માટે પ્રકારોને વ્યાખ્યાયિત કરવા માટે નીચેના સામગ્રી સાથે src/.env.d.ts ફાઇલ બનાવો અથવા અપડેટ કરો:

/// <reference path="../.astro/types.d.ts" />

interface ImportMetaEnv {
  readonly PUBLIC_SUPABASE_URL: string;
  readonly PUBLIC_SUPABASE_ANON_KEY: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

આ ખાતરી કરે છે કે ટાઇપસ્ક્રિપ્ટને જાણે કે કયા પર્યાવરણ ચલની અપેક્ષા રાખવી.

.env ફાઇલ બનાવવી

આગળ, તમારા પ્રોજેક્ટની મૂળ પર .env ફાઇલ બનાવો:

PUBLIC_SUPABASE_URL="<YOUR_SUPABASE_URL>"
PUBLIC_SUPABASE_ANON_KEY="<YOUR_SUPABASE_ANON_KEY>"

ફાઇલમાં રજૂ કરેલ સ્થાનધારકોને તમારા વાસ્તવિક સુપાબેસ URL અને અનામિક કી સાથે બદલવો. આ ફાઇલ તમારા સંવેદનશીલ કીોને તમારા સોર્સ કોડમાંથી બહાર રાખશે અને તમારા એપ્લિકેશનમાં તેમને ઍક્સેસ કરવાની એક સુસંગત રીત પ્રદાન કરશે.


3. સુપાબેસ યુટિલિટી બનાવવી

સુપાબેસ સાથે કામ કરવું સરળ બનાવવા માટે, અમે સુપાબેસ ક્લાયન્ટ, ઓથેન્ટિકેશન, અને ડેટા પ્રદાતા માટે યુટિલિટી ફાઇલો બનાવશું.

સુપરબેસ ક્લાયન્ટ સેટઅપ.

સુપાબેસ ક્લાયન્ટને આરંભ કરવા માટે src/utils/supabase.ts બનાવો:

import { createClient } from '@supabase/supabase-js';

export const supabaseClient = createClient(
  import.meta.env.PUBLIC_SUPABASE_URL,
  import.meta.env.PUBLIC_SUPABASE_ANON_KEY
);

આ ફાઇલ એ પર્યાવરણ ચલનો ઉપયોગ કરીને સુપાબેસ ક્લાયન્ટને સેટ અપ કરે છે.

ઓથ પ્રોવાઇડર.

આગળ, સુપાબેસ સાથે ઓથેન્ટિકેશન સંભાળવા માટે src/utils/authProvider.ts બનાવો:

import { supabaseAuthProvider } from 'ra-supabase';
import { supabaseClient } from './supabase';

export const authProvider = supabaseAuthProvider(supabaseClient, {
  getIdentity: async (user) => {
    const { data, error } = await supabaseClient
      .from('userprofile')
      .select('id, user_id, name')
      .match({ email: user.email })
      .single();

    if (!data || error) {
      throw new Error();
    }

    return {
      id: data.user_id,
      fullName: data.name,
    };
  },
});

આ પ્રદાતા વપરાશકર્તા ઓથેન્ટિકેશનને મેનેજ કરશે અને તેને તમારા UserProfile ટેબલ સાથે સુપાબેસમાં જોડશે.

ડેટા પ્રોવાઇડર.

અંતે, રિએક્ટ એડમિનને સુપાબેસ સાથે જોડવા માટે src/utils/dataProvider.ts બનાવો:

import { supabaseDataProvider } from 'ra-supabase';
import { supabaseClient } from './supabase';

export const dataProvider = supabaseDataProvider({
  instanceUrl: import.meta.env.PUBLIC_SUPABASE_URL,
  apiKey: import.meta.env.PUBLIC_SUPABASE_ANON_KEY,
  supabaseClient,
});

ડેટા પ્રદાતા તમામ CRUD કાર્યને સંભાળે છે, તમારા રિએક્ટ એડમિન ઘટકોને સીધા સુપાબેસ સાથે જોડે છે.


4. એડમિન ઈન્ટરફેસ બનાવવો

હવે કે અમારી બેકએન્ડ તૈયાર છે, એડમિન ઈન્ટરફેસ બનાવવાનો સમય આવી ગયો છે.

એડમિન એપ બનાવવી.

અમે મુખ્ય એડમિન એપ્લિકેશન સેટ કરવાથી શરૂ કરીશું. src/components/Admin/AdminApp.tsx બનાવો::

import { Admin, CustomRoutes, Resource } from "react-admin";
import { Route } from 'react-router-dom';
import { LoginPage, SetPasswordPage, ForgotPasswordPage } from "ra-supabase";
import { authProvider } from "../../utils/authProvider";
import { dataProvider } from "../../utils/dataProvider";
import { PostCreate } from "./Post/PostCreate";
import { PostEdit } from "./Post/PostEdit";
import { PostList } from "./Post/PostList";

const AdminApp = () => (
  <Admin
    dataProvider={dataProvider}
    authProvider={authProvider}
    loginPage={LoginPage}
  >
    <CustomRoutes noLayout>
      <Route path={SetPasswordPage.path} element={<SetPasswordPage />} />
      <Route path={ForgotPasswordPage.path} element={<ForgotPasswordPage />} />
    </CustomRoutes>
    <Resource
      name="posts"
      list={PostList}
      edit={PostEdit}
      create={PostCreate}
      recordRepresentation="title"
    />
  </Admin>
);

export default AdminApp;

આ ઘટક મુખ્ય એડમિન ઈન્ટરફેસને સેટ કરે છે, લોગિન, પાસવર્ડ રીસેટ, અને પોસ્ટ્સ માટે CRUD કાર્યો સંભાળે છે.

પોસ્ટ બનાવવાની અને એડિટ કરવાની ફોર્મ્સ સેટઅપ કરવી.

બનાવવો અને સંપાદન કરવાના ફોર્મ્સ તમને બ્લોગ પોસ્ટ્સને મેનેજ કરવા દે છે. પ્રથમ, src/components/Admin/Post/PostCreate.tsx બનાવો:

import {
  Create,
  SimpleForm,
  TextInput,
  DateInput,
  required,
  ImageInput,
  ImageField,
  useNotify,
  useRedirect,
  useDataProvider,
} from 'react-admin';
import { RichTextInput } from 'ra-input-rich-text';
import { supabaseClient } from '../../../utils/supabase';

export const PostCreate = () => {
  const notify = useNotify();
  const redirect = useRedirect();
  const dataProvider = useDataProvider();

  const handleSave = async (values: any) => {
    try {
      let updatedFeaturedImages = values.featured_images || [];

      // Handle single image input correctly (if it's not an array yet)
      if (!Array.isArray(updatedFeaturedImages) && updatedFeaturedImages.rawFile) {
        updatedFeaturedImages = [updatedFeaturedImages];
      }

      // Check if there are new images to upload
      if (updatedFeaturedImages.length > 0) {
        const uploadedImages = [];

        for (const image of updatedFeaturedImages) {
          if (image.rawFile) {
            const file = image.rawFile;
            const fileName = `${file.name}-${Date.now()}`;
            const { data, error } = await supabaseClient
              .storage
              .from('media')
              .upload(`public/${fileName}`, file);

            if (error) {
              throw new Error('Error uploading image: ' + error.message);
            }

            // Get the public URL of the uploaded image
            const { data: { publicUrl } } = supabaseClient
              .storage
              .from('media')
              .getPublicUrl(`public/${fileName}`);

            uploadedImages.push({ src: publicUrl, title: image.title || file.name });
          }
        }

        updatedFeaturedImages = uploadedImages;
      }

      // Save the post data with updated featured images
      const updatedValues = { ...values, featured_images: updatedFeaturedImages };
      dataProvider.create('posts', { data: updatedValues }).then(({ data }) => {
        notify('Post created successfully');
        redirect('list', 'posts');
      });
    } catch (error: any) {
      notify(`Error: ${error.message}`, { type: 'warning' });
    }
  };

  return (
    <Create>
      <SimpleForm onSubmit={handleSave}>
        <TextInput source="title" validate={[required()]} />
        <ImageInput source="featured_images" label="Featured Images" multiple>
          <ImageField source="src" title="title" />
        </ImageInput>
        <TextInput source="excerpt" validate={[required()]} multiline />
        <RichTextInput source="content" />
        <DateInput
          label="Publication date"
          source="publish_date"
          defaultValue={new Date()}
        />
      </SimpleForm>
    </Create>
  );
};

હવે, src/components/Admin/Post/PostEdit.tsx પર સંપાદન ફોર્મ બનાવો:

import {
  Edit,
  SimpleForm,
  TextInput,
  DateInput,
  required,
  ImageInput,
  ImageField,
  useNotify,
  useRedirect,
  useDataProvider,
  useGetRecordId,
  useGetOne,
} from "react-admin";
import { RichTextInput } from "ra-input-rich-text";
import { supabaseClient } from "../../../utils/supabase";

export const PostEdit = () => {
  const notify = useNotify();
  const redirect = useRedirect();
  const dataProvider = useDataProvider();
  const recordId = useGetRecordId();

  // Fetch the previous values using useGetOne
  const { data: previousValues, isLoading } = useGetOne('posts', { id: recordId });

  
  const handleSave = async (values: any) => {
    try {
      let updatedFeaturedImages = values.featured_images || [];
      if (!Array.isArray(updatedFeaturedImages) && updatedFeaturedImages.rawFile) {
        updatedFeaturedImages = [updatedFeaturedImages];
      }

      // Check if there are new images to upload
      if (updatedFeaturedImages && updatedFeaturedImages.length > 0) {
        const uploadedImages = [];

        for (const image of updatedFeaturedImages) {
          if (image.rawFile) {
            const file = image.rawFile;
            const fileName = `${file.name}-${Date.now()}`;
            const { data, error } = await supabaseClient
              .storage
              .from('media')
              .upload(`public/${fileName}`, file);

            if (error) {
              throw new Error('Error uploading image: ' + error.message);
            }

            // Get the public URL of the uploaded image
            const { data: { publicUrl } } = supabaseClient
              .storage
              .from('media')
              .getPublicUrl(`public/${fileName}`);

            uploadedImages.push({ src: publicUrl, title: image.title || file.name });
          } else {
            // If image is already uploaded, keep it as is
            uploadedImages.push(image);
          }
        }

        updatedFeaturedImages = uploadedImages;
      }

      // Save the post data with updated featured images
      const updatedValues = { ...values, featured_images: updatedFeaturedImages };
      dataProvider.update('posts', {
        id: previousValues.id,
        data: updatedValues,
        previousData: previousValues,  // Pass previous data here
      }).then(({ data }) => {
        notify('Post updated successfully');
        redirect('list', 'posts');
      });
    } catch (error: any) {
      notify(`Error: ${error.message}`, { type: 'warning' });
    }
  };

  if (isLoading) return null;

  return (
    <Edit>
      <SimpleForm onSubmit={handleSave}>
        <TextInput
          style={{ display: "none" }}
          disabled
          hidden
          label="Id"
          source="id"
        />
        <TextInput source="title" validate={required()} />
        <ImageInput source="featured_images">
          <ImageField source="src" title="title" />
        </ImageInput>
        <TextInput source="excerpt" validate={[required()]} multiline />
        <TextInput source="slug" validate={required()} />
        <TextInput source="unique_id" readOnly validate={required()} />
        <RichTextInput source="content" validate={required()} />
        <DateInput label="Publication date" source="publish_date" />
      </SimpleForm>
    </Edit>
  );
};

પોસ્ટ્સ પ્રદર્શિત અને મેનેજ કરવી.

અંતે, તમારા પોસ્ટ્સને બતાવવા માટે એક સરળ યાદી સેટ કરીએ. src/components/Admin/PostList.tsx બનાવો:

import {
  List,
  Datagrid,
  TextField,
  DateField,
  ArrayField,
  SingleFieldList,
  ImageField,
} from "react-admin";
import PostUrl from "./PostUrl";

export const PostList = () => (
  <List>
    <Datagrid>
      <TextField source="title" />
      <PostUrl />
      <ArrayField source="featured_images" label="Featured Image">
        <SingleFieldList>
          <ImageField source="src" title="title" />
        </SingleFieldList>
      </ArrayField>
      <TextField source="excerpt" />
      <DateField source="publish_date" />
    </Datagrid>
  </List>
);

પ્રત્યેક પોસ્ટ માટે તેનો સ્લગ અને અનન્ય ID આધારે URL જનરેટ કરવા માટે અહીં એક સહાયક ઘટક છે. src/components/Admin/Post/PostUrl.tsx બનાવો:

import { useRecordContext } from 'react-admin';

const PostUrl = (props: { label?: string }) => {
  const record = useRecordContext();
  if (!record || !record.slug || !record.unique_id) return null;
  const url = `/${record.slug}-${record.unique_id}/`;
  return (
    <a href={url} target="_blank" rel="noopener noreferrer">
      {props.label ?? url}
    </a>
  );
};

export default PostUrl;

5. એસ્ટ્રોમાં એડમિન ઈન્ટરફેસને સંકલિત કરવો

અંતે, આ એડમિન ઈન્ટરફેસને તમારા એસ્ટ્રો એપ્લિકેશન સાથે કનેક્ટ કરીએ.

એડમિન રૂટ બનાવવો

src/pages/admin/[...slug]/index.astro પર એક નવો રૂટ બનાવો:

---
import AdminApp from "../../../components/Admin/AdminApp";
---
<AdminApp client:only="react" />

આ રૂટ એડમિન ઈન્ટરફેસને રેન્ડર કરશે જ્યારે પણ વપરાશકર્તા /admin પર જાય છે.


ભાગ 3 પૂરું કરવું

અને એફિન! તમે એસ્ટ્રો, રિએક્ટ, અને ટેલવિન્ડ CSS નો ઉપયોગ કરીને એક શક્તિશાળી એડમિન ઈન્ટરફેસ સફળતાપૂર્વક સેટ કરી દીધું છે, જે સુપાબેસ સાથે બેકએન્ડમાં જોડાયું છે. આ એડમિન પેનલ તમારા બ્લોગની સામગ્રી પર સંપૂર્ણ નિયંત્રણ પ્રદાન કરશે, બનાવવું, સંપાદન કરવું, અને પોસ્ટ્સને મેનેજ કરવું સરળ બનાવશે.

ભાગ 4માં, અમે રિએક્ટ અને ટેલવિન્ડનો ઉપયોગ કરીને બ્લોગના જાહેર ભાગને સેટઅપ કરવા પર ધ્યાન કેન્દ્રિત કરીશું, ખાતરી કરીશું કે તમારું સામગ્રી સારી રીતે કાર્ય કરવાની સાથે-साथ સારી રીતે દેખાય છે.


આગળ: ભાગ 4 – રિએક્ટ અને ટેલવિન્ડ સાથે ફ્રન્ટએન્ડ બનાવવું

હવે કે એડમિન પેનલ સેટ છે, ચાલો તમારા બ્લોગ માટે એક સ્લીક, પ્રતિસાદી ફ્રન્ટએન્ડ બનાવીએ!


વધુ માહિતી જોઈએ?

રિએક્ટ એડમિનને સુપાબેસ સાથે કેવી રીતે ઉપયોગ કરવો અથવા તમારા એસ્ટ્રો સેટઅપને કેવી રીતે વિસ્તૃત કરવો તે વિશે વધુ વિગતો માટે, સુપાબેસ ડોક્યુમેન્ટેશન અને એસ્ટ્રો ડોક્યુમેન્ટેશન તપાસો.