Skip to Content
Technical Articles

MicroBlogging App using SAP Business App Studio – Part 1

In this blog, I will show you how to set up the project in SAP Business Application Studio, install the prerequisites and we will start building the user interface.

At the end of this blog, we will have a simple UI that can show a list of mock posts. In the series of later blogs, we will be building a microblogging application. We will be building both the frontend and the backend. For building the frontend we will use React, Typescript, and Tailwind.

Both the frontend and the backend service as well as the database will be built and hosted on SAP Business Technology Platform.

Microblogging sites are popular because they’re serving content in formats that today’s users find palatable and SAP BTP is the platform for the Intelligent Enterprise.

Customers can achieve agility, business value, and continual innovation through integration, data to value, and extensibility of all SAP and third-party applications and data assets.

 

Prerequisites:

 

Getting started:

  1. Create a folder(root package) inside Projects in Business Application Studio inside
    SAP Mobile Application Dev Space

      2. Open your terminal and type the following commands to create a typescript project.

#Using NPM

• npx create-react-app tailwindreact-app --template typescript

 

Once done, we get following output:

 

3. cd into your app directory

 

4. Within this directory, run npm start to run the app locally:

 

5. Set up Tailwind CSS

Install dependencies

npm install tailwindcss postcss autoprefixer

– Note: if see error like `Error: PostCSS plugin tailwindcss requires PostCSS 8.`,
uninstall Tailwind and re-install using the compatibility build instead:

npm uninstall tailwindcss postcss autoprefixer
npm install tailwindcss@npm:@tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

 

 

7. Set up tailwind.config and postcss.config files

npx tailwindcss init -p   

This command further generates these 2 files:

 

8. Update tailwind.config.js file, provide an array of paths to all of your template files using the purge option:

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

 

9. Now whenever you compile your CSS with NODE_ENV set to production, Tailwind will automatically purge unused styles from your CSS

 

 

10. Update package.json file for dependencies

{
  "name": "tailwindreact-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.4",
    "@testing-library/react": "^11.1.0",
    "@testing-library/user-event": "^12.1.10",
    "autoprefixer": "^10.2.4",
    "postcss": "^8.2.6",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.2",
    "tailwindcss": "^2.0.3",
    "typescript": "^4.1.5",
    "web-vitals": "^1.0.1"
  },
  "scripts": {
    "build:tailwind": "tailwindcss build src/css/tailwind.css -o src/css/tailwind.output.css",
    "prestart": "npm run build:tailwind",
    "prebuild": "npm run build:tailwind",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "@types/react": "^17.0.1",
    "@types/react-dom": "^17.0.0",
    "@types/react-router-dom": "^5.1.7"
  }
}

 

 

11. Inside your src folder create a folder, name it css , this is where all your styles would be stored. Inside that folder, create a tailwind.css .

@tailwind is a Tailwind directive that is used to inject default base styles, components, utilities and custom configurations.

@tailwind base — This injects Tailwind’s base styles, which is a combination of Normalize.css and some additional base styles

@tailwind components — This injects any component (small reusable styles like buttons and form elements, etc) classes registered by plugins defined in your tailwind config file.

@tailwind utilities — This injects all of Tailwind’s utility classes(including the default and your own utilities) they are generated based on your config file

 

12. Now run the command npm start, this will generate a tailwind.output.css file under css folder

 

13. Update the index.tsx file with the following code:

import React from 'react';
import ReactDOM from 'react-dom';
import './css/tailwind.output.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

 

 

14. We will create a component folder inside the src folder and create 2 files inside this:

 

15. Copy and paste the following in NewPost.tsx

import React from 'react';

export function NewPost() {
    return <div>New Post Comment</div>
}

 

16. Copy and paste the following in PostList.tsx to create the mock data to run the app locally however we need an interface for this that we will create now:

import React from 'react';
import * as api from '../api/posts';
import Post from './Post';

const MockPosts: api.Post[] = [
    {
        id: 'AXY_001',
        text: 'Hello Everyone! Welcome to my first: Microblogging App Page',
        createdAt: '2021-02-06T05:56:09Z',
        authorId: '001',
        authorUserName: '@Shiromani',
        authorDisplayName: 'Shiromani Soni',
        authorImageUrl: '*******',
        likeCount: 2,
        replyCount: 3,
        attachmentType: 'image',
        attachmentUrl: '*******',
    },
    
    {
        id: 'AXY_002',
        text: 'Sample Post',
        createdAt: '2021-02-10T05:56:09Z',
        authorId: '002',
        authorUserName: '@John',
        authorDisplayName: 'John Rowlands',
        likeCount:  0,
        replyCount: 0,
    },


];

export function PostList() {
    return (
    <ul className='border border-t-8 divide-y divide-grey-300'>
        {MockPosts.map((p) => {
            return (
            <li key={p.id} className='px-6 py-4'>
                <Post post={p} />
            </li>
            );
        })}
    </ul>
    );
}

AuthorImageUrl and AttachmentUrl can be passed of your choice.

 

17. We will create an interface called api inside src folder and a file called posts.ts:

 

18. Copy and Paste the following code in posts.ts

type AttachmentType = 'image' | 'video' | { custom: string }

export interface Post {

    id: string;
    text: string;
    createdAt: string;
    authorId: string;
    authorUserName: string;
    authorDisplayName: string;
    authorImageUrl?: string;
    likeCount: number;
    replyCount: number;
    conversationId?: string;
    replies?: Post[];
    attachmentUrl?: string;
    attachmentType?: AttachmentType;




}

 

19. Now, to render these post components, we will create a post.tsx file inside components folder which will represent a post:

 

Copy and Paste the following code in Post.tsx file:

import React from 'react';
import * as api from '../api/posts';
import { getRelativeTime } from '../utils/time';

type postProps = {
    post: api.Post;
};

function PhotoPlaceHolder() {
    return (
        <svg
        className='block w-12 h-12 mt-1'
        viewBox='0 0 50 50'
        fill='none'
        xmlns='http://www.w3.org/2000/svg'
        >
        <circle cx='25' cy='25' r='25' fill='#4A5568'/>
        </svg>
    );
}


export default function Post({ post }: postProps) {
const {
    authorDisplayName,
    authorImageUrl,
    authorUserName,
    createdAt,
    text,
    attachmentType,
    attachmentUrl,
} = post;


return ( 
<div className='flex'>
    <div className= 'block w-12 h-12'>
        {
         authorImageUrl ? (
        <img className='rounded-full' 
         src={authorImageUrl} 
         alt={authorDisplayName}
         />
            ) : (
                <PhotoPlaceHolder></PhotoPlaceHolder>
            )
        }
    </div>
    
    <div className='ml-2'>
        <div>
        <span className='font-semibold text-black cursor-pointer hover:underline'>{authorDisplayName}</span>
        <span className='ml-1 text-gray-600'>{authorUserName}</span>
        <span className='mx-1 text-gray-600'>.</span>
        <span className='text-gray-600 cursor-pointer hover:underline'>
            {getRelativeTime(createdAt)}
        </span>
        </div>

        {attachmentType && attachmentType === 'image' && (
        <div className='mt-2'>
        <img alt={text} src={attachmentUrl}/>
        </div>
        )}
        </div>
    </div>
);
}

 

20. Now, create a Home.tsx file inside src folder and paste the following code:

import React from 'react';
import { NewPost } from './components/NewPost';
import { PostList } from './components/PostList';

export function Home() {
    return (
        <div className='h-screen bg-white'>
            <h1 className='text-3xl text-center text-grey-900'>Home Page</h1>
            <div className="flex justify-center mx-2 mt-12">
                <div className='max-w-xl'>
                    <NewPost></NewPost>
                    <PostList></PostList>
                </div>
            </div>
        </div>
    );
}

 

 

21. To make the date fields user friendly, I have written a math function.

Please create a utils folder inside src folder and create a time.ts file inside as shown below:

and now paste the following code in time.ts file

export function getRelativeTime(dateString: string): string {
    const msPerMinute = 60 * 1000;
    const msPerHour = msPerMinute * 60;
    const msPerDay = msPerHour * 24;

    const elapsed = Date.now() - Date.parse(dateString);

    if (elapsed < msPerMinute) {
        return Math.round(elapsed / 1000) + 's';
    }
    else if (elapsed < msPerHour) {
        return Math.round(elapsed / msPerMinute) + 'm';
    }
    else if (elapsed < msPerDay) {
        return Math.round(elapsed / msPerHour) + 'h';
    }
    else {
        const d = new Date(dateString);
        return d.toLocaleString('default', { month: 'short', day: 'numeric'})
    }
}

 

 

22. Finally, it’s time ro run the app locally and test:

use command npm start

 

Hurray,

 

 

Reference Links:

 

Install Tailwind CSS with Create React App: https://tailwindcss.com/docs/guides/create-react-app

 

How to use Tailwind CSS in React to configure create-react-app: https://blog.logrocket.com/create-react-app-and-tailwindcss/

 

I hope this blog would be useful for everyone looking forward to work on SAP Business Application Studio and explore the concept of Bring Your Own Programming Language(BYOL).

Please do share your views on this.

Profile on SAP Community:

shiromani soni

 

Regards

Shiromani Soni

Be the first to leave a comment
You must be Logged on to comment or reply to a post.