Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bec06a7
feat: initial admin interface with username/password auth, and announ…
mahfuzhannan Nov 20, 2025
20430d0
fix: added check to see if admin page is enabled or not, and show use…
mahfuzhannan Nov 20, 2025
f794915
fix: type script warnings failing build, cleaned these up
mahfuzhannan Nov 20, 2025
b549c4e
fix: correct the conditional logic for if the admin interface is visi…
mahfuzhannan Nov 20, 2025
d91f6b1
fix: ts issue with try catch error, set type to any to resolve
mahfuzhannan Nov 20, 2025
496ad0a
fix: updated the docs and example .env with updated google service ac…
mahfuzhannan Nov 20, 2025
e4ee826
fix: announcement will be shown if the current time is the same as st…
mahfuzhannan Nov 20, 2025
5bc0a76
fix: correct the modal sizes to fit number plates
mahfuzhannan Nov 20, 2025
f97243e
docs: updated docs to mention service account requires permission to …
mahfuzhannan Nov 20, 2025
9f6a441
docs: corrected some typos
mahfuzhannan Nov 20, 2025
7770295
fix: clean up ts typings and few other bug fixes mentioned as part of…
mahfuzhannan Nov 22, 2025
d97c308
fix: clean up ts typings and few other bug fixes mentioned as part of…
mahfuzhannan Nov 22, 2025
36dff72
fix: use moment when setting the dates for admin annoucement via the …
mahfuzhannan Dec 3, 2025
9e958be
fix: correct the metadata template, and add metadata for the admin page
mahfuzhannan Dec 3, 2025
bfc8f7f
fix: add required fields for the announcement for to avoid black anno…
mahfuzhannan Dec 3, 2025
a43799d
feat: migrate announcement data logic to separate configuration sheet…
mahfuzhannan Dec 5, 2025
d2a0094
Merge remote-tracking branch 'mosque-os/main' into feat/car-announcem…
mahfuzhannan Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,16 @@
#MOSQUE_API_ENDPOINT=https://api.mosque.tech/mosque-data/1o9dngtGJbfkFGZK_M7xdlo2PtRuQknGEQU3FxpiPVbg # Google sheets version
MOSQUE_API_ENDPOINT=http://localhost:3000/api/example-mosque

# Add your brand colors
THEME_COLOR_HIGHLIGHT=#29B0EA
THEME_COLOR_PRIMARY=#003750
THEME_COLOR_PRIMARY_ALT=#086182

# Admin Screen config
ADMIN_GOOGLE_SA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n******\n-----END PRIVATE KEY-----\n"
ADMIN_GOOGLE_SA_EMAIL=XXXX@XXXX-XXXX.iam.gserviceaccount.com
SPREADSHEET_ID=1o9dngtGJbfkFGZK_M7xdlo2PtRuQknGEQU3FxpiPVbg

AUTH_USERNAME=myuser
AUTH_PASSWORD=secret
AUTH_SECRET=some-long-random-string
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# misc
.DS_Store
*.pem
/.idea

# debug
npm-debug.log*
Expand Down
123 changes: 96 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,26 @@

In the Name of Allah the Merciful, the Compassionate.

This project has been open-sourced as a form of sadaqah jariyah - may Allah reward every single contribution, technical, non-technical and those who share with others.
This project has been open-sourced as a form of sadaqah jariyah - may Allah reward every single contribution, technical,
non-technical and those who share with others.

## Introduction

This application allows mosques to run a prayer display screen for the worshipers and also an offline progressive web app that runs on any modern web browser.
This application allows mosques to run a prayer display screen for the worshipers and also an offline progressive web
app that runs on any modern web browser.

This version of the application supersedes the [original version](https://github.com/Mosque-Screens/Mosque-Screen) which was created in association with [East London Mosque](https://www.eastlondonmosque.org.uk/).
This version of the application supersedes the [original version](https://github.com/Mosque-Screens/Mosque-Screen) which
was created in association with [East London Mosque](https://www.eastlondonmosque.org.uk/).

The commentary on why we built this version, can be found in the following blog: [https://medium.com/mosque/design-concept-direction-for-mosque-screens-51c4f9bb82](https://medium.com/mosque/design-concept-direction-for-mosque-screens-51c4f9bb82).
The commentary on why we built this version, can be found in the following
blog: [https://medium.com/mosque/design-concept-direction-for-mosque-screens-51c4f9bb82](https://medium.com/mosque/design-concept-direction-for-mosque-screens-51c4f9bb82).

The original contributors of this project can be found [here](https://github.com/Mosque-Screens/Mosque-Screen#contributors-wall-of-fame).
The original contributors of this project can be
found [here](https://github.com/Mosque-Screens/Mosque-Screen#contributors-wall-of-fame).

A special thanks should be given to the [UK Government Digital Service](https://www.gov.uk/government/organisations/government-digital-service) who provided voluntary days which allowed the original project to come to life.
A special thanks should be given to
the [UK Government Digital Service](https://www.gov.uk/government/organisations/government-digital-service) who provided
voluntary days which allowed the original project to come to life.

## Features

Expand All @@ -41,17 +48,16 @@ All of the code sits here:

<img src="./public/demo-mosque-view-2.png" />


### Mobile app

<img src="./public/demo-mobile-view.png" width="500px" />


## How to get set up as a Mosque

### Prerequisites

- Google Account
- Google Cloud Project (Optional for Admin interface)

### Step 1: Make a copy of the prayer times spreadsheet

Expand All @@ -60,7 +66,8 @@ Go to the following link and make a copy of the spreadsheet:

### Step 2: Share "viewer" access to the spreadsheet with our Google Account

Click on the share button and add `mosque.screens786@gmail.com` as a viewer. We don't need any write access, so please do not give us this.
Click on the share button and add `mosque.screens786@gmail.com` as a viewer. We don't need any write access, so please
do not give us this.

This allows our API to access your spreadsheet and read your data.

Expand Down Expand Up @@ -89,7 +96,8 @@ https://api.mosque.tech/mosque-data/1o9dngtGJbfkFGZK_M7xdlo2PtRuQknGEQU3FxpiPVbg
You can use the following tool to automatically generate an API endpoint:
https://codepen.io/DilwoarH/full/mdvOexr

Note: You don't need to use our API endpoint, you can generate your own endpoint but please make sure it has all the required properties.
Note: You don't need to use our API endpoint, you can generate your own endpoint but please make sure it has all the
required properties.

### Step 4: Deploy your app

Expand All @@ -104,7 +112,58 @@ Click on the following button:
Once your app has deployed, visit the URL and test your screen.
Make sure it works on the TV you want to use for the mosque. Our app is designed for 1080p Full HD TV screens.

### Optional things you might want to do
## Admin Interface Setup

The Admin interface will allow you to manage the screen from your phone these include:

- Showing Announcements

### Prerequisites

- Google Account
- Google Cloud Project (Optional for Admin interface)

### Step 1: Setup Google Cloud project

You can follow the steps provided by Google here:
https://developers.google.com/workspace/guides/create-project

### Step 2: Enable the Google Sheets API in Google Cloud

You can follow the steps provided by Google here:
https://developers.google.com/workspace/guides/enable-apis

### Step 3: Create a service account & env vars

You'll need to download your private key which will be part of the credentials.json file,
that you'll receive after creating the service account.

In your deployment environment vars set

- `ADMIN_GOOGLE_SA_PRIVATE_KEY` as the private key in your environment vars
- `ADMIN_GOOGLE_SA_EMAIL` as the newly created service account email

```
e.g.

ADMIN_GOOGLE_SA_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n******\n-----END PRIVATE KEY-----\n"
ADMIN_GOOGLE_SA_EMAIL=XXXX@XXXX-XXXX.iam.gserviceaccount.com
```

You'll also need to set a username and password for authentication, these will also be stored in env vars

```
AUTH_USERNAME=myuser
AUTH_PASSWORD=secret
```

### Step 4. Share "editor" access to the spreadsheet with your new service account

Click on the share button on the Google sheet and enter your service account.

You can now deploy the app and see if you can launch the admin page.

## Optional things you might want to do

#### Custom domain

Expand All @@ -115,17 +174,22 @@ If you want to update your domain, you can do so by following the Vercel documen

#### Environment variables

|KEY|VALUE|DEFAULT|DESCRIPTION|
|-|-|-|-|
|MOSQUE_API_ENDPOINT|https://api.mosque.tech/mosque-data/1o9dngtGJbfkFGZK_M7xdlo2PtRuQknGEQU3FxpiPVbg|REQUIRED - NO DEFAULT|Data from Mosque API|
|BLACKOUT_PERIOD|13|13 minutes|How long your mosque screen dims / blacks out during congregation prayer|
|UPCOMING_PRAYER_DAY|3|3 upcoming days shown in slider|How many upcoming days it shows in the sliding section|
|SLIDE_TRANSITION_TIME|7|7 seconds|How long each slide shows for in the sliding section|
|THEME_COLOR_PRIMARY|#0F715D|#0F715D|The primary color to be used for the background of the screen|
|THEME_COLOR_PRIMARY_ALT|#0C5A4B|#0C5A4B|The primary alternative color to be used for the containers/cards on the screen|
|THEME_COLOR_ON_PRIMARY|#FFFFFF|#FFFFFF|The text color to be used when the background color is primary|
|THEME_COLOR_ON_PRIMARY_ALT|#FFFFFF|#FFFFFF|The text color to be used when the background color is primary alternative|
|THEME_COLOR_HIGHLIGHT|#10b981|#10b981|The color used to highlight upcoming prayer|
| KEY | VALUE | DEFAULT | DESCRIPTION |
|-----------------------------|----------------------------------------------------------------------------------|---------------------------------|---------------------------------------------------------------------------------|
| MOSQUE_API_ENDPOINT | https://api.mosque.tech/mosque-data/1o9dngtGJbfkFGZK_M7xdlo2PtRuQknGEQU3FxpiPVbg | REQUIRED - NO DEFAULT | Data from Mosque API |
| BLACKOUT_PERIOD | 13 | 13 minutes | How long your mosque screen dims / blacks out during congregation prayer |
| UPCOMING_PRAYER_DAY | 3 | 3 upcoming days shown in slider | How many upcoming days it shows in the sliding section |
| SLIDE_TRANSITION_TIME | 7 | 7 seconds | How long each slide shows for in the sliding section |
| THEME_COLOR_PRIMARY | #0F715D | #0F715D | The primary color to be used for the background of the screen |
| THEME_COLOR_PRIMARY_ALT | #0C5A4B | #0C5A4B | The primary alternative color to be used for the containers/cards on the screen |
| THEME_COLOR_ON_PRIMARY | #FFFFFF | #FFFFFF | The text color to be used when the background color is primary |
| THEME_COLOR_ON_PRIMARY_ALT | #FFFFFF | #FFFFFF | The text color to be used when the background color is primary alternative |
| THEME_COLOR_HIGHLIGHT | #10b981 | #10b981 | The color used to highlight upcoming prayer |
| ADMIN_GOOGLE_SA_PRIVATE_KEY | "-----BEGIN PRIVATE KEY-----\n******\n-----END PRIVATE KEY-----\n" | | Required as part of Admin interface to interact with your google sheets |
| ADMIN_GOOGLE_SA_EMAIL | XXXX@XXXX-XXXX.iam.gserviceaccount.com | | Required as part of Admin interface to interact with your google sheets |
| AUTH_USERNAME | myuser | myuser | Required as part of Admin interface to login to admin page |
| AUTH_PASSWORD | secret | secret | Required as part of Admin interface to login to admin page |
| AUTH_SECRET | | | Required as part of Admin interface to encrypt auth JWT token |

## Dev set up

Expand All @@ -143,16 +207,19 @@ npm run dev

## Raspberry Pi Setup

Raspberry Pi (RPI) is an easy way to get the screen running, the screen doesn't need too much power - a lightweight computer like an RPI is enough.
Raspberry Pi (RPI) is an easy way to get the screen running, the screen doesn't need too much power - a lightweight
computer like an RPI is enough.

You can buy one from the official suppliers: https://www.raspberrypi.com/products/

We recommend you buy a case with a fan or some heat-cooling solution - the screen will run all day so it's good to have a good cooling solution.
We recommend you buy a case with a fan or some heat-cooling solution - the screen will run all day so it's good to have
a good cooling solution.

### RPI set up steps

0. Install [Raspberry Pi OS](https://www.raspberrypi.com/software/) on the SD Card
1. Install [chromium-browser](https://www.chromium.org/getting-involved/download-chromium) - **Do this step only if you do not have Chromium**
1. Install [chromium-browser](https://www.chromium.org/getting-involved/download-chromium) - **Do this step only if you
do not have Chromium**
2. Open Terminal
3. `cd .config`
4. `sudo mkdir -p lxsession/LXDE-pi`
Expand All @@ -173,6 +240,8 @@ point-rpi

## Still need help?

We don't provide any free support, you can join our discord channel to get help from the community using the following invite link: [https://discord.gg/CG7frj2](https://discord.gg/CG7frj2).
We don't provide any free support, you can join our discord channel to get help from the community using the following
invite link: [https://discord.gg/CG7frj2](https://discord.gg/CG7frj2).

If you would like paid support, you can contact us here for pricing: [mosque.screens786@gmail.com](mailto:mosque.screens786@gmail.com).
If you would like paid support, you can contact us here for
pricing: [mosque.screens786@gmail.com](mailto:mosque.screens786@gmail.com).
12 changes: 12 additions & 0 deletions app/admin/SessionProviderWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"use client"

import { SessionProvider } from "next-auth/react"

/**
* So that we don't have to wrap the whole app in a session provider and keep
* @param children
* @constructor
*/
export default function SessionProviderWrapper({ children } : {children: React.ReactNode}) {
return <SessionProvider>{children}</SessionProvider>
}
57 changes: 57 additions & 0 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { getMetaData } from "@/services/MosqueDataService"
import { MosqueMetadataType } from "@/types/MosqueDataType"
import { Metadata } from "next"
import AdminPage from '@/components/Admin/AdminPage'
import SessionProviderWrapper from '@/app/admin/SessionProviderWrapper'
import { getSession, isAdminInterfaceEnabled } from '@/app/auth'
import { redirect } from 'next/navigation'


export const metadata = {
title: "Admin",
description: "Admin interface for MosqueScreen Project by MosqueOS",
};

export default async function AdminServerPage() {


return (
<SessionProviderWrapper>
<AdminPageWrapper />
</SessionProviderWrapper>
)

}

async function AdminPageWrapper() {
const isAdminEnabled = isAdminInterfaceEnabled()

if (!isAdminEnabled) {
return (
<div className="min-h-screen flex items-center justify-center bg-mosqueBrand-primary px-6">
<div className="text-center text-mosqueBrand-onPrimary">
<div className="text-4xl mb-3">⚠️</div>
<h1 className="text-2xl font-semibold mb-2">Admin Interface Disabled</h1>
<p className="text-sm opacity-80 max-w-sm mx-auto">
Please contact the system administrator.
</p>
</div>
</div>

)
}

const session = await getSession()

if (!session) {
redirect("/api/auth/signin")
}

const mosqueMetadata: MosqueMetadataType = await getMetaData()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for metadata fetch.

The getMetaData() call can fail (network errors, API issues, etc.), but there's no error handling. If it throws, the entire admin page will crash with an unhandled exception.

Wrap the fetch in a try-catch block:

+  let mosqueMetadata: MosqueMetadataType
+  try {
+    mosqueMetadata = await getMetaData()
+  } catch (error) {
+    return (
+      <div className="min-h-screen flex items-center justify-center bg-red-50 px-6">
+        <div className="text-center">
+          <h1 className="text-2xl font-semibold mb-2 text-red-700">Error Loading Data</h1>
+          <p className="text-sm text-red-600">Unable to fetch mosque metadata. Please try again later.</p>
+        </div>
+      </div>
+    )
+  }
-  const mosqueMetadata: MosqueMetadataType = await getMetaData()

  return (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mosqueMetadata: MosqueMetadataType = await getMetaData()
let mosqueMetadata: MosqueMetadataType
try {
mosqueMetadata = await getMetaData()
} catch (error) {
return (
<div className="min-h-screen flex items-center justify-center bg-red-50 px-6">
<div className="text-center">
<h1 className="text-2xl font-semibold mb-2 text-red-700">Error Loading Data</h1>
<p className="text-sm text-red-600">Unable to fetch mosque metadata. Please try again later.</p>
</div>
</div>
)
}
🤖 Prompt for AI Agents
In app/admin/page.tsx around line 50, the direct call const mosqueMetadata:
MosqueMetadataType = await getMetaData() lacks error handling and will crash the
page if getMetaData() throws; wrap the await in a try-catch, catch and log the
error (or surface it to your logger), and provide a safe fallback path—either
set mosqueMetadata to a sensible default/null and render an error state, or
rethrow/return a 500/notFound response after logging, so the page does not crash
on fetch failures.


return (
<div className="bg-white min-w-full min-h-screen">
<AdminPage metadata={mosqueMetadata} />
</div>
)
}
6 changes: 6 additions & 0 deletions app/api/auth/[...nextauth]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { authOptions } from "@/app/auth"
import NextAuth from "next-auth"

const handler = NextAuth(authOptions)

export { handler as GET, handler as POST }
20 changes: 20 additions & 0 deletions app/api/data/announcements/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { sheetsGetAnnouncement, isSheetsClientReady } from '@/services/GoogleSheetsService';
import {
getAnnouncement,
} from '@/services/MosqueDataService'

import { type NextRequest } from 'next/server'

export async function GET(request: NextRequest,) {
try {
let announcement = null;
// call the sheets API if we are connected, saves having to call external API
if (await isSheetsClientReady()) {
announcement = await sheetsGetAnnouncement()
} else {
announcement = await getAnnouncement()
}
return Response.json({ announcement })
} catch (error: any) {
return Response.json({ error: error?.message ?? "Unknown error" }, { status: 400 }); }
}
Loading